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.utils.translation import gettext_lazy as _
from django.apps import AppConfig
class PhotobriefingAppConfig(AppConfig):
name = "outbo.apps.photobriefing"
label = "photobriefing"
verbose_name = _("Photo briefing")
default_app_config = "outbo.apps.photobriefing.PhotobriefingAppConfig"
+21
View File
@@ -0,0 +1,21 @@
from django.contrib import admin
from nested_admin import nested
from outbo.apps.photobriefing.models import Brief, BriefItem, BriefItemPhoto, Category
class BriefItemPhotoInline(nested.NestedStackedInline):
model = BriefItemPhoto
class BriefItemInline(nested.NestedStackedInline):
model = BriefItem
inlines = [BriefItemPhotoInline]
@admin.register(Brief)
class BriefAdmin(nested.NestedModelAdmin):
inlines = [BriefItemInline]
admin.site.register(Category)
+7
View File
@@ -0,0 +1,7 @@
from enum import Enum
class ChoiceRole(Enum):
INVALID = "<invalid>"
SUPPLIER = "supplier"
CLIENT = "client"
@@ -0,0 +1,304 @@
# Generated by Django 2.2.7 on 2019-11-29 08:57
from django.db import migrations, models
import django.db.models.deletion
import django_fsm
import outbo.apps.core.fields
import outbo.apps.core.utils
import outbo.apps.photobriefing.enums
import outbo.apps.photobriefing.models.brief_item_photo
import rules.contrib.models
class Migration(migrations.Migration):
initial = True
dependencies = [
("products", "0001_initial"),
]
operations = [
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="photo_brief",
related_query_name="photo_brief",
serialize=False,
to="products.Product",
verbose_name="product",
),
),
(
"session_date",
models.DateField(
default=outbo.apps.core.utils.get_current_date,
verbose_name="photo session date",
),
),
("concept_description", models.TextField(blank=True, default="")),
(
"stage",
django_fsm.FSMField(
default="created", max_length=50, protected=True
),
),
],
options={"abstract": False,},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.CreateModel(
name="BriefItem",
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"
),
),
("title", models.CharField(max_length=100)),
("description", models.TextField()),
(
"brief",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="items",
related_query_name="item",
to="photobriefing.Brief",
verbose_name="brief",
),
),
],
options={"ordering": ["id"],},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.CreateModel(
name="Category",
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"
),
),
(
"label",
models.CharField(
max_length=30,
primary_key=True,
serialize=False,
verbose_name="label",
),
),
(
"description",
models.CharField(max_length=30, verbose_name="description"),
),
],
options={"verbose_name": "category", "verbose_name_plural": "categories",},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.CreateModel(
name="BriefItemPhoto",
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.photobriefing.models.brief_item_photo.filename,
verbose_name="file",
),
),
(
"item",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="photos",
related_query_name="photo",
to="photobriefing.BriefItem",
verbose_name="brief item",
),
),
],
options={"abstract": False,},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.AddField(
model_name="briefitem",
name="category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="photobriefing.Category",
verbose_name="category",
),
),
migrations.CreateModel(
name="BriefItemChoice",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created_at",
outbo.apps.core.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="Created at"
),
),
(
"updated_at",
outbo.apps.core.fields.ModificationDateTimeField(
auto_now=True, verbose_name="Updated at"
),
),
(
"deleted_at",
outbo.apps.core.fields.RemovalDateTimeField(
blank=True, editable=False, null=True, verbose_name="Deleted at"
),
),
(
"role",
models.CharField(
choices=[
(
outbo.apps.photobriefing.enums.ChoiceRole("<invalid>"),
"<invalid>",
),
(
outbo.apps.photobriefing.enums.ChoiceRole("supplier"),
"supplier",
),
(
outbo.apps.photobriefing.enums.ChoiceRole("client"),
"client",
),
],
default=outbo.apps.photobriefing.enums.ChoiceRole("<invalid>"),
editable=False,
max_length=30,
),
),
(
"brief",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="choices",
related_query_name="choice",
to="photobriefing.Brief",
verbose_name="brief",
),
),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="choices+",
related_query_name="choice",
to="photobriefing.Category",
verbose_name="category",
),
),
(
"item",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="choices",
related_query_name="choice",
to="photobriefing.BriefItem",
verbose_name="item",
),
),
],
options={"unique_together": {("brief", "category", "role")},},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.2.8 on 2019-12-18 14:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("photobriefing", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="brief",
name="concept_description",
field=models.TextField(blank=True),
),
]
@@ -0,0 +1,92 @@
# Generated by Django 2.2.10 on 2020-05-25 08:25
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("photobriefing", "0002_auto_20191218_1505"),
]
operations = [
migrations.AlterField(
model_name="brief",
name="product",
field=models.OneToOneField(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
primary_key=True,
related_name="photo_brief",
related_query_name="photo_brief",
serialize=False,
to="products.Product",
verbose_name="product",
),
),
migrations.AlterField(
model_name="briefitem",
name="brief",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="items",
related_query_name="item",
to="photobriefing.Brief",
verbose_name="brief",
),
),
migrations.AlterField(
model_name="briefitem",
name="category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="photobriefing.Category",
verbose_name="category",
),
),
migrations.AlterField(
model_name="briefitemchoice",
name="brief",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="choices",
related_query_name="choice",
to="photobriefing.Brief",
verbose_name="brief",
),
),
migrations.AlterField(
model_name="briefitemchoice",
name="category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="choices+",
related_query_name="choice",
to="photobriefing.Category",
verbose_name="category",
),
),
migrations.AlterField(
model_name="briefitemchoice",
name="item",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="choices",
related_query_name="choice",
to="photobriefing.BriefItem",
verbose_name="item",
),
),
migrations.AlterField(
model_name="briefitemphoto",
name="item",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="photos",
related_query_name="photo",
to="photobriefing.BriefItem",
verbose_name="brief item",
),
),
]
Whitespace-only changes.
@@ -0,0 +1,5 @@
from .brief import Brief
from .brief_item import BriefItem
from .brief_item_photo import BriefItemPhoto
from .brief_item_choice import BriefItemChoice
from .category import Category
+107
View File
@@ -0,0 +1,107 @@
from itertools import product
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_fsm import FSMField, transition
from rules.contrib.models import RulesModel
from outbo.apps.activity_log.enums import MessageType
from outbo.apps.activity_log.models import ProductLogItem
from outbo.apps.core.models import TimestampableDeletableMixin
from outbo.apps.core.utils import get_current_date, is_empty_set
from outbo.apps.final_photobriefing.models import FinalBrief
from outbo.apps.photobriefing import rules
from outbo.apps.photobriefing.enums import ChoiceRole
class Brief(TimestampableDeletableMixin, RulesModel):
class Meta:
rules_permissions = {
"add": rules.can_create_brief,
"view": rules.can_read_brief,
"change": rules.can_update_brief,
"delete": rules.can_delete_brief,
}
product = models.OneToOneField(
verbose_name=_("product"),
to="products.Product",
on_delete=models.CASCADE,
primary_key=True,
editable=False,
related_name="photo_brief",
related_query_name="photo_brief",
)
session_date = models.DateField(
verbose_name=_("photo session date"),
blank=False,
null=False,
default=get_current_date,
)
concept_description = models.TextField(blank=True, null=False)
stage = FSMField(default="created", protected=True)
def save(self, *args, **kwargs):
result = super().save(*args, **kwargs)
if self.product:
self.product.save()
return result
def acceptable_for_user(self, *, user):
if user.is_observer or user.is_trader:
return False
return not user.is_agency and self.stage == "published"
def publishable_for_user(self, *, user):
if user.is_observer or user.is_trader:
return False
all_categories = set(self.items.values_list("category", flat=True))
if self.editable_for_user(user=user) and not is_empty_set(all_categories):
return True
return False
def completable_for_user(self, *, user):
if user.is_observer or user.is_trader:
return False
if not user.is_agency or self.stage != "published":
return False
roles = [ChoiceRole.SUPPLIER, ChoiceRole.CLIENT]
all_categories = set(self.items.values_list("category", flat=True))
if is_empty_set(all_categories):
return False
for category, role in product(all_categories, roles):
if not self.choices.filter(category_id=category, role=role).exists():
return False
return True
def editable_for_user(self, *, user):
if user.is_observer or user.is_trader:
return False
if not user.is_agency or not self.stage == "created":
return False
return self.product.stage.group in [2, 3]
@transition(field="stage", source="created", target="published")
def publish_as_user(self, *, user):
ProductLogItem.objects.create(
product=self.product,
created_by=user,
message="Photobriefing published",
message_type=MessageType.SUCCESS,
)
@transition(field="stage", source="published", target="completed")
def complete_as_user(self, *, user):
FinalBrief.objects.create(brief=self)
ProductLogItem.objects.create(
product=self.product,
created_by=user,
message="Photobriefing completed",
message_type=MessageType.SUCCESS,
)
@@ -0,0 +1,32 @@
from django.db import models
from rules.contrib.models import RulesModel
from outbo.apps.core.models import TimestampableDeletableMixin
from django.utils.translation import gettext_lazy as _
from outbo.apps.photobriefing import rules
class BriefItem(TimestampableDeletableMixin, RulesModel):
class Meta:
rules_permissions = {
"add": rules.can_create_brief_item,
"view": rules.can_read_brief_item,
"change": rules.can_update_brief_item,
"delete": rules.can_delete_brief_item,
}
ordering = ["id"]
brief = models.ForeignKey(
verbose_name=_("brief"),
to="Brief",
on_delete=models.CASCADE,
related_name="items",
related_query_name="item",
)
category = models.ForeignKey(
verbose_name=_("category"), to="Category", on_delete=models.CASCADE
)
title = models.CharField(max_length=100, blank=False, null=False)
description = models.TextField(blank=False, null=False)
@@ -0,0 +1,39 @@
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.photobriefing.enums import ChoiceRole
class BriefItemChoice(TimestampableDeletableMixin, RulesModel):
class Meta:
unique_together = (("brief", "category", "role"),)
brief = models.ForeignKey(
verbose_name=_("brief"),
to="Brief",
on_delete=models.CASCADE,
related_name="choices",
related_query_name="choice",
)
category = models.ForeignKey(
verbose_name=_("category"),
to="Category",
on_delete=models.CASCADE,
related_name="choices+",
related_query_name="choice",
)
role = models.CharField(
max_length=30,
choices=[(role, role.value) for role in ChoiceRole],
default=ChoiceRole.INVALID,
editable=False,
)
item = models.ForeignKey(
verbose_name=_("item"),
to="BriefItem",
on_delete=models.CASCADE,
related_name="choices",
related_query_name="choice",
)
@@ -0,0 +1,40 @@
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
def filename(self, filename):
return "/".join(
(
"projects",
self.item.brief.product.project.slug[1:],
"photobriefing",
filename,
)
)
class BriefItemPhoto(TimestampableDeletableMixin, RulesModel):
class Meta:
rules_permissions = {
"view": always_allow,
}
item = models.ForeignKey(
verbose_name=_("brief item"),
to="BriefItem",
on_delete=models.CASCADE,
related_name="photos",
related_query_name="photo",
)
file = models.ImageField(verbose_name=_("file"), upload_to=filename)
@property
def filename(self):
return os.path.basename(self.file.name)
+17
View File
@@ -0,0 +1,17 @@
from django.db import models
from rules.contrib.models import RulesModel
from outbo.apps.core.models import TimestampableDeletableMixin
from django.utils.translation import gettext_lazy as _, pgettext
class Category(TimestampableDeletableMixin, RulesModel):
class Meta:
verbose_name = _("category")
verbose_name_plural = _("categories")
label = models.CharField(verbose_name=_("label"), max_length=30, primary_key=True)
description = models.CharField(verbose_name=_("description"), max_length=30)
def __str__(self):
return pgettext("photo_brief_categories", self.description)
+27
View File
@@ -0,0 +1,27 @@
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.final_photobriefing.enums import PhotoType
class Photo(TimestampableDeletableMixin, RulesModel):
brief = models.ForeignKey(
verbose_name=_("photo brief"),
to="Brief",
on_delete=models.CASCADE,
related_name="photos",
related_query_name="photo",
)
type = models.CharField(
max_length=50,
choices=((type, type.value) for type in PhotoType),
default=PhotoType.INVALID,
)
def filename(self, filename):
return f"projects/{self.brief.product.project.slug[1:]}/photos/{filename}"
file = models.ImageField(verbose_name=_("file"), upload_to=filename)
+63
View File
@@ -0,0 +1,63 @@
from typing import TYPE_CHECKING
from rules import always_deny, predicate
from outbo.apps.core.rules import is_agency
from outbo.apps.products.rules import can_read_product
if TYPE_CHECKING:
from outbo.apps.authentication.models import User
from outbo.apps.photobriefing.models import Brief, BriefItem
# Brief
@predicate
def _has_readable_product(user: "User", brief: "Brief"):
return can_read_product.test(user, brief.product)
@predicate
def can_edit_brief(user: "User", brief: "Brief"):
return brief.editable_for_user(user=user)
@predicate
def can_publish_brief(user: "User", brief: "Brief"):
return brief.publishable_for_user(user=user)
@predicate
def can_complete_brief(user: "User", brief: "Brief"):
return brief.completable_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_publish_brief | can_accept_brief | can_complete_brief
)
can_delete_brief = always_deny
# BriefItem
@predicate
def _has_readable_brief(user: "User", brief_item: "BriefItem"):
return can_read_brief.test(user, brief_item.brief)
@predicate
def _has_editable_brief(user: "User", brief_item: "BriefItem"):
return can_edit_brief.test(user, brief_item.brief)
# CRUD
can_create_brief_item = is_agency
can_read_brief_item = _has_readable_brief
can_update_brief_item = always_deny
can_delete_brief_item = _has_editable_brief
@@ -0,0 +1,5 @@
from .brief_item_photo import BriefItemPhotoSerializer
from .brief_item import BriefItemSerializer
from .brief_item_choice import BriefItemChoiceSerializer
from .brief import BriefSerializer
from .category import CategorySerializer
+159
View File
@@ -0,0 +1,159 @@
from typing import TYPE_CHECKING
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from outbo.apps.photobriefing.enums import ChoiceRole
from outbo.apps.photobriefing.models import Brief, BriefItemChoice
from outbo.apps.photobriefing.rules import can_complete_brief, can_publish_brief
from outbo.apps.photobriefing.serializers import (
BriefItemSerializer,
BriefItemChoiceSerializer,
)
if TYPE_CHECKING:
from outbo.apps.authentication.models import User
class BaseBriefSerializer(serializers.ModelSerializer):
class Meta:
model = Brief
def get_editable(self, instance: Brief):
current_user = self.context["request"].user
return instance.editable_for_user(user=current_user)
def get_acceptable(self, instance: Brief):
current_user = self.context["request"].user
return instance.acceptable_for_user(user=current_user)
def get_publishable(self, instance: Brief):
current_user = self.context["request"].user
return instance.publishable_for_user(user=current_user)
def get_completable(self, instance: Brief):
current_user = self.context["request"].user
return instance.completable_for_user(user=current_user)
class BriefSerializer:
class Retrieve(BaseBriefSerializer):
items = BriefItemSerializer.List(many=True)
choices = BriefItemChoiceSerializer.List(many=True)
editable = serializers.SerializerMethodField()
acceptable = serializers.SerializerMethodField()
publishable = serializers.SerializerMethodField()
completable = serializers.SerializerMethodField()
class Meta(BaseBriefSerializer.Meta):
fields = [
"stage",
"session_date",
"concept_description",
"items",
"choices",
"created_at",
"updated_at",
"editable",
"acceptable",
"publishable",
"completable",
]
class Update(BaseBriefSerializer):
publish = serializers.BooleanField()
complete = serializers.BooleanField()
choice = BriefItemChoiceSerializer.Update(partial=False)
items = BriefItemSerializer.Create(many=True)
class Meta(BaseBriefSerializer.Meta):
fields = [
"session_date",
"concept_description",
"items",
"choice",
"publish",
"complete",
]
def update(self, instance, validated_data):
if validated_data.get("choice", None) is not None:
serializer = BriefSerializer.ChoiceUpdate(context=self.context)
return serializer.update(instance, {"choice": validated_data["choice"]})
if validated_data.get("publish", None) is not None:
serializer = BriefSerializer.Publish(context=self.context)
return serializer.update(
instance, {"publish": validated_data["publish"]}
)
if validated_data.get("complete", None) is not None:
serializer = BriefSerializer.Complete(context=self.context)
return serializer.update(
instance, {"complete": validated_data["complete"]}
)
items = validated_data.pop("items", None)
if items:
for item in items:
item["brief"] = instance
BriefItemSerializer.Create(context=self.context).create(item)
return super().update(instance=instance, validated_data=validated_data)
class Publish(BaseBriefSerializer):
publish = serializers.BooleanField()
class Meta(BaseBriefSerializer.Meta):
fields = ["publish"]
def update(self, instance, validated_data):
publish = validated_data["publish"]
current_user = self.context["request"].user
if not can_publish_brief.test(current_user, instance):
raise PermissionDenied()
if publish:
instance.publish_as_user(user=current_user)
instance.save()
return instance
class Complete(BaseBriefSerializer):
complete = serializers.BooleanField()
class Meta(BaseBriefSerializer.Meta):
fields = ["complete"]
def update(self, instance: Brief, validated_data):
complete = validated_data["complete"]
current_user = self.context["request"].user
if not can_complete_brief.test(current_user, instance):
raise PermissionDenied()
if complete:
instance.complete_as_user(user=current_user)
instance.save()
return instance
class ChoiceUpdate(BaseBriefSerializer):
choice = BriefItemChoiceSerializer.Update()
class Meta(BaseBriefSerializer.Meta):
fields = ["choice"]
def update(self, instance, validated_data):
choice = validated_data["choice"]
current_user: "User" = self.context["request"].user
if current_user.is_supplier:
role = ChoiceRole.SUPPLIER
elif current_user.is_client:
role = ChoiceRole.CLIENT
else:
raise NotImplemented
current_choice, _ = BriefItemChoice.objects.get_or_create(
defaults={"item": choice["item"]},
brief=instance,
category=choice["category"],
role=role,
)
if current_choice.item != choice["item"]:
current_choice.item = choice["item"]
current_choice.save()
return instance
@@ -0,0 +1,38 @@
from rest_framework import serializers
from outbo.apps.photobriefing.models import BriefItem
from outbo.apps.photobriefing.serializers import BriefItemPhotoSerializer
class BaseBriefItemSerializer(serializers.ModelSerializer):
class Meta:
model = BriefItem
class BriefItemSerializer:
class List(BaseBriefItemSerializer):
photos = BriefItemPhotoSerializer.List(many=True)
class Meta(BaseBriefItemSerializer.Meta):
fields = ["id", "category", "title", "description", "photos"]
class Create(BaseBriefItemSerializer):
photos = BriefItemPhotoSerializer.Create(many=True)
class Meta(BaseBriefItemSerializer.Meta):
fields = ["category", "title", "description", "photos"]
def create(self, validated_data):
photos = validated_data.pop("photos", None)
instance = super().create(validated_data)
if photos is not None:
for photo in photos:
photo["item"] = instance
BriefItemPhotoSerializer.Create().create(photo)
return instance
class AfterCreate(BaseBriefItemSerializer):
photos = BriefItemPhotoSerializer.List(many=True)
class Meta(BaseBriefItemSerializer.Meta):
fields = ["id", "category", "title", "description", "photos"]
@@ -0,0 +1,30 @@
from rest_framework import serializers
from outbo.apps.photobriefing.enums import ChoiceRole
from outbo.apps.photobriefing.models import BriefItemChoice
class BaseBriefItemChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = BriefItemChoice
class BriefItemChoiceSerializer:
class List(BaseBriefItemChoiceSerializer):
role = serializers.SerializerMethodField()
class Meta(BaseBriefItemChoiceSerializer.Meta):
fields = ["category", "item", "role"]
def get_role(self, instance):
role = eval(instance.role) # TODO: eval is evil
if role is ChoiceRole.SUPPLIER:
return "supplier"
if role is ChoiceRole.CLIENT:
return "client"
raise NotImplementedError
class Update(BaseBriefItemChoiceSerializer):
class Meta:
model = BriefItemChoice
fields = ["category", "item"]
@@ -0,0 +1,23 @@
from rest_framework import serializers
from outbo.apps.photobriefing.models import BriefItemPhoto
class BaseBriefItemPhotoSerializer(serializers.ModelSerializer):
class Meta:
model = BriefItemPhoto
class BriefItemPhotoSerializer:
class Create(BaseBriefItemPhotoSerializer):
class Meta(BaseBriefItemPhotoSerializer.Meta):
fields = ["file"]
class List(BaseBriefItemPhotoSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="photobriefing:briefitemphoto-detail", read_only=True
)
filename = serializers.ReadOnlyField()
class Meta(BaseBriefItemPhotoSerializer.Meta):
fields = ["url", "filename"]
@@ -0,0 +1,10 @@
from rest_framework import serializers
from outbo.apps.photobriefing.models import Category
class CategorySerializer:
class List(serializers.ModelSerializer):
class Meta:
model = Category
fields = ["label", "description"]
+24
View File
@@ -0,0 +1,24 @@
from rest_framework_extensions.routers import ExtendedSimpleRouter
from outbo.apps.photobriefing.viewsets import (
BriefViewSet,
BriefItemNestedViewSet,
CategoryViewSet,
BriefItemPhotoViewSet,
BriefItemViewSet,
)
app_name = "photobriefing"
router = ExtendedSimpleRouter()
router.register(prefix="photobriefing-categories", viewset=CategoryViewSet)
router.register(prefix="photobriefing-items", viewset=BriefItemViewSet)
router.register(prefix="photobriefing", viewset=BriefViewSet).register(
prefix="items",
viewset=BriefItemNestedViewSet,
basename="brief-item",
parents_query_lookups=["brief_id"],
)
router.register(prefix="photobriefing-photos", viewset=BriefItemPhotoViewSet)
urlpatterns = router.urls
+82
View File
@@ -0,0 +1,82 @@
from django.http import HttpResponseRedirect
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import AllowAny
from outbo.apps.core.viewsets import (
AutoPermissionViewSetMixin,
RetrieveModelMixin,
MultipleSerializerViewSet,
PartialUpdateModelMixin,
NestedViewSetMixin,
CreateModelMixin,
ListModelMixin,
DestroyModelMixin,
)
from outbo.apps.photobriefing import rules
from outbo.apps.photobriefing.models import Brief, BriefItem, Category, BriefItemPhoto
from outbo.apps.photobriefing.serializers import (
BriefSerializer,
BriefItemSerializer,
CategorySerializer,
BriefItemPhotoSerializer,
)
class BriefViewSet(
AutoPermissionViewSetMixin,
PartialUpdateModelMixin,
RetrieveModelMixin,
MultipleSerializerViewSet,
):
queryset = Brief.objects.all()
serializer_class_set = BriefSerializer
class BriefItemViewSet(
AutoPermissionViewSetMixin, DestroyModelMixin, MultipleSerializerViewSet,
):
queryset = BriefItem.objects.all()
serializer_class_set = BriefItemSerializer
class BriefItemNestedViewSet(
NestedViewSetMixin,
AutoPermissionViewSetMixin,
CreateModelMixin,
MultipleSerializerViewSet,
):
queryset = BriefItem.objects.all()
serializer_class_set = BriefItemSerializer
def perform_create(self, *, serializer):
current_user = self.request.user
parents = self.get_parents_query_dict()
brief = Brief.objects.get(pk=parents["brief_id"])
if not rules.can_edit_brief.test(current_user, brief):
raise PermissionDenied() # NOTE: Should it be here?
return serializer.save(brief=brief)
class BriefItemPhotoViewSet(
AutoPermissionViewSetMixin, RetrieveModelMixin, MultipleSerializerViewSet,
):
queryset = BriefItemPhoto.objects.all()
serializer_class_set = BriefItemPhotoSerializer
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: BriefItemPhoto = self.get_object()
return HttpResponseRedirect(redirect_to=photo.file.url)
class CategoryViewSet(
AutoPermissionViewSetMixin, ListModelMixin, MultipleSerializerViewSet,
):
queryset = Category.objects.all()
serializer_class_set = CategorySerializer