Initial commit from ZIP
This commit is contained in:
commit
378d4692a9
454 files changed
+40367
No files matched your search
Whitespace-only changes.
Executable
+18
@@ -0,0 +1,18 @@
|
||||
from nested_admin.nested import NestedTabularInline
|
||||
from polymorphic.admin import (
|
||||
PolymorphicParentModelAdmin as BasePolymorphicParentModelAdmin,
|
||||
)
|
||||
|
||||
|
||||
class PolymorphicParentModelAdmin(BasePolymorphicParentModelAdmin):
|
||||
def get_child_type_choices(self, request, action):
|
||||
choices = super().get_child_type_choices(request=request, action=action)
|
||||
return tuple((ct_id, label.title()) for (ct_id, label) in choices)
|
||||
|
||||
|
||||
class NestedTabularSingleItemInline(NestedTabularInline):
|
||||
template = "nesting/admin/inlines/tabular_single.html"
|
||||
|
||||
|
||||
class NestedStackedSingleItemInline(NestedTabularInline):
|
||||
template = "nesting/admin/inlines/stacked_single.html"
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
from functools import partial
|
||||
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django_extensions.db.fields import (
|
||||
CreationDateTimeField as BaseCreationDateTimeField,
|
||||
ModificationDateTimeField as BaseModificationDateTimeField,
|
||||
)
|
||||
from django_fsm import FSMFieldMixin
|
||||
|
||||
from outbo.apps.core.forms import EnumChoiceField
|
||||
|
||||
|
||||
class CreationDateTimeField(BaseCreationDateTimeField):
|
||||
"""
|
||||
CreationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, auto_now_add=True
|
||||
"""
|
||||
|
||||
|
||||
class ModificationDateTimeField(BaseModificationDateTimeField):
|
||||
"""
|
||||
ModificationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, auto_now=True
|
||||
|
||||
Sets value to now every time the object is saved.
|
||||
"""
|
||||
|
||||
|
||||
class RemovalDateTimeField(models.DateTimeField):
|
||||
"""
|
||||
RemovalDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, null=True
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("editable", False)
|
||||
kwargs.setdefault("blank", True)
|
||||
kwargs.setdefault("null", True)
|
||||
models.DateTimeField.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
if self.editable is not False:
|
||||
kwargs["editable"] = True
|
||||
if self.blank is not True:
|
||||
kwargs["blank"] = False
|
||||
if self.null is not True:
|
||||
kwargs["null"] = False
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
class FSMKeyField(FSMFieldMixin, models.ForeignKey):
|
||||
def get_state(self, instance):
|
||||
return instance.__dict__[self.attname]
|
||||
|
||||
def set_state(self, instance, state):
|
||||
instance.__dict__[self.attname] = self.to_python(state)
|
||||
|
||||
|
||||
class EnumCharField(models.CharField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
enum = kwargs.pop("enum", None)
|
||||
if enum is not None:
|
||||
if kwargs.get("choices", None) is not None:
|
||||
raise ValueError("Cannot provide choices for EnumCharField")
|
||||
kwargs["choices"] = [(item, item.value) for item in enum]
|
||||
super().__init__(*args, **kwargs)
|
||||
self.validators.pop() # lengthvalidator not necessary on this one
|
||||
self.enum = enum
|
||||
|
||||
def get_internal_type(self):
|
||||
return "CharField"
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
enum_form_class = partial(EnumChoiceField, enum=self.enum)
|
||||
defaults = {
|
||||
"widget": forms.Select,
|
||||
"form_class": enum_form_class,
|
||||
"choices": [(str(item), item.value) for item in self.enum],
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return super().formfield(**defaults)
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
kwargs["enum"] = self.enum
|
||||
if "choices" in kwargs:
|
||||
del kwargs["choices"]
|
||||
|
||||
return name, path, args, kwargs
|
||||
|
||||
def to_python(self, value):
|
||||
value = super().to_python(value)
|
||||
if value is not None:
|
||||
if not isinstance(value, self.enum):
|
||||
enum_name, enum_value = value.split(".", 1)
|
||||
if enum_name == self.enum.__name__:
|
||||
return self.enum[enum_value]
|
||||
return value
|
||||
|
||||
def get_prep_value(self, value):
|
||||
value = super().get_prep_value(value)
|
||||
if value is None:
|
||||
return value
|
||||
|
||||
if isinstance(value, self.enum):
|
||||
return str(value)
|
||||
return value
|
||||
|
||||
def from_db_value(self, value, *args):
|
||||
if value is not None:
|
||||
enum_name, enum_value = value.split(".", 1)
|
||||
if enum_name == self.enum.__name__:
|
||||
return self.enum[enum_value]
|
||||
return value
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class EnumChoiceField(forms.ChoiceField):
|
||||
def __init__(self, enum, **kwargs):
|
||||
kwargs.setdefault("choices", [(item, item.value) for item in enum])
|
||||
super().__init__(**kwargs)
|
||||
self.enum = enum
|
||||
|
||||
def prepare_value(self, value):
|
||||
if isinstance(value, self.enum):
|
||||
return str(value)
|
||||
return value
|
||||
|
||||
def to_python(self, value):
|
||||
value = super().to_python(value)
|
||||
if not isinstance(value, self.enum):
|
||||
try:
|
||||
enum_name, enum_value = value.split(".", 1)
|
||||
if enum_name == self.enum.__name__:
|
||||
value = self.enum[enum_value]
|
||||
except ValueError:
|
||||
raise ValidationError(self.error_messages["invalid"], code="invalid")
|
||||
return value
|
||||
|
||||
def clean(self, value):
|
||||
value = super().clean(value)
|
||||
return self.enum(value)
|
||||
Whitespace-only changes.
Whitespace-only changes.
+236
@@ -0,0 +1,236 @@
|
||||
from typing import TYPE_CHECKING, Tuple, Iterable
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from faker import Factory
|
||||
|
||||
from tests.factories.authentication import (
|
||||
UserFactory,
|
||||
AgencyUserFactory,
|
||||
ClientUserFactory,
|
||||
SupplierUserFactory,
|
||||
)
|
||||
from tests.factories.organizations import (
|
||||
CategoryFactory,
|
||||
BrandFactory,
|
||||
)
|
||||
|
||||
from tests.factories.products import ProductStageFactory, PackagingTypeFactory
|
||||
from tests.factories.token import TokenFactory
|
||||
|
||||
faker = Factory()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.organizations.models import ClientOrganization
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Seed with some existing data"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--dev", action="store_true", help="Seed with dev data")
|
||||
|
||||
def __make_users_and_organizations(self, *, password=None):
|
||||
self.stdout.write("Seeding organizations and users...")
|
||||
|
||||
if password is None:
|
||||
password = "password"
|
||||
|
||||
UserFactory(
|
||||
username="admin",
|
||||
password=password,
|
||||
email="admin@example.com",
|
||||
is_superuser=True,
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
agency = AgencyUserFactory(
|
||||
username="agency",
|
||||
password=password,
|
||||
email="agency@example.com",
|
||||
organization__name="Sample agency",
|
||||
)
|
||||
|
||||
client = ClientUserFactory(
|
||||
username="client",
|
||||
password=password,
|
||||
email="client@example.com",
|
||||
organization__name="Sample client",
|
||||
)
|
||||
|
||||
supplier = SupplierUserFactory(
|
||||
username="supplier",
|
||||
password=password,
|
||||
email="supplier@example.com",
|
||||
organization__name="Sample supplier",
|
||||
)
|
||||
|
||||
TokenFactory(user=agency, key="6f9afaa36ee8b224181dc14e2ca9af33e97c0f71")
|
||||
TokenFactory(user=client, key="481372f60325289029679df25c72e6ccd05892ed")
|
||||
TokenFactory(user=supplier, key="d23d8f187614c8a76fe7316470e6f50ff5bc4188")
|
||||
|
||||
def __make_product_stages(self):
|
||||
self.stdout.write("Seeding product stages...")
|
||||
|
||||
ProductStageFactory(
|
||||
id="waiting_for_brief",
|
||||
label="1",
|
||||
group=1,
|
||||
description="Briefing",
|
||||
next_action="Oczekuje na uzupełnienie briefu",
|
||||
order=1,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_brief_accept_by_agency",
|
||||
label="1A",
|
||||
group=1,
|
||||
description="Akceptacja agencji",
|
||||
next_action="Oczekuje na akceptację agencji",
|
||||
order=2,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_brief_accept_by_client",
|
||||
label="1B",
|
||||
group=1,
|
||||
description="Akceptacja klienta",
|
||||
next_action="Oczekuje na akceptację klienta",
|
||||
order=3,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_draft",
|
||||
label="2A",
|
||||
group=2,
|
||||
description="Publikacja draftu",
|
||||
next_action="Oczekuje na publikację draftu",
|
||||
order=4,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_draft_accept_by_supplier",
|
||||
label="2B",
|
||||
group=2,
|
||||
description="Akceptacja dostawcy",
|
||||
next_action="Oczekuje na akceptację dostawcy",
|
||||
order=5,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_draft_accept_by_client",
|
||||
label="2C",
|
||||
group=2,
|
||||
description="Akceptacja klienta",
|
||||
next_action="Oczekuje na akceptację klientget_random_stringa",
|
||||
order=6,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_ff",
|
||||
label="3A",
|
||||
group=3,
|
||||
description="Publikacja FF",
|
||||
next_action="Oczekuje na publikację draftu",
|
||||
order=7,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_ff_accept_by_supplier",
|
||||
label="3B",
|
||||
group=3,
|
||||
description="Akceptacja dostawcy",
|
||||
next_action="Oczekuje na akceptację dostawcy",
|
||||
order=8,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_ff_accept_by_client",
|
||||
label="3C",
|
||||
group=3,
|
||||
description="Akceptacja klienta",
|
||||
next_action="Oczekuje na akceptację klienta",
|
||||
order=9,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_pf",
|
||||
label="4A",
|
||||
group=4,
|
||||
description="Publikacja printing files",
|
||||
next_action="Oczekuje na publikację printing files",
|
||||
order=10,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_pf_accept_by_supplier",
|
||||
label="4B",
|
||||
group=4,
|
||||
description="Akceptacja dostawcy",
|
||||
next_action="Oczekuje na akceptację dostawcy",
|
||||
order=11,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="waiting_for_pf_accept_by_client",
|
||||
label="4C",
|
||||
group=4,
|
||||
description="Akceptacja klienta",
|
||||
next_action="Oczekuje na akceptację klienta",
|
||||
order=12,
|
||||
)
|
||||
ProductStageFactory(
|
||||
id="completed",
|
||||
label="5",
|
||||
group=5,
|
||||
description="Zako\u0144czono",
|
||||
next_action="Oczekuje na archiwizację",
|
||||
order=13,
|
||||
)
|
||||
|
||||
def __make_packaging_types(self, *, items: Iterable[str] = None):
|
||||
self.stdout.write("Seeding packaging types...")
|
||||
if items:
|
||||
for name in items:
|
||||
PackagingTypeFactory(name=name)
|
||||
else:
|
||||
PackagingTypeFactory.create_batch(size=20)
|
||||
|
||||
def __make_brands(
|
||||
self, *, items: Iterable[Tuple[str, "ClientOrganization"]] = None
|
||||
):
|
||||
self.stdout.write("Seeding brands...")
|
||||
if items:
|
||||
for name, client in items:
|
||||
BrandFactory(name=name, client=client)
|
||||
else:
|
||||
BrandFactory.create_batch(size=20)
|
||||
|
||||
def __make_categories(self, *, items: Iterable[str] = None):
|
||||
self.stdout.write("Seeding categories...")
|
||||
if items:
|
||||
for item in items:
|
||||
CategoryFactory(name=item)
|
||||
else:
|
||||
CategoryFactory.create_batch(size=20)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write("Seeding the database...")
|
||||
|
||||
if not options["dev"]:
|
||||
packaging_types = (
|
||||
"Carton/gift box",
|
||||
"Header",
|
||||
"Hangtag",
|
||||
"Paper band",
|
||||
"Paper board",
|
||||
"Blister",
|
||||
"Image insert",
|
||||
"Simple band",
|
||||
"Label/sticker",
|
||||
"Simple CDU/Displaybox",
|
||||
)
|
||||
raise NotImplementedError()
|
||||
|
||||
password = "!secret123"
|
||||
|
||||
self.__make_users_and_organizations(password=password)
|
||||
self.__make_product_stages()
|
||||
self.__make_packaging_types()
|
||||
self.__make_brands()
|
||||
self.__make_categories()
|
||||
|
||||
self.stdout.write("Seeding completed.")
|
||||
self.stdout.write("Credentials:")
|
||||
self.stdout.write(f" admin: {password}")
|
||||
self.stdout.write(f" agency: {password}")
|
||||
self.stdout.write(f" client: {password}")
|
||||
self.stdout.write(f" supplier: {password}")
|
||||
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 2.2.16 on 2021-01-21 14:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EmailLog",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||
),
|
||||
),
|
||||
("sender", models.CharField(max_length=254)),
|
||||
("receiver", models.CharField(max_length=254)),
|
||||
("time", models.DateTimeField()),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("sending", "sending"),
|
||||
("sent", "sent"),
|
||||
("queued", "queued"),
|
||||
("invalid", "invalid"),
|
||||
("rejected", "rejected"),
|
||||
("failed", "failed"),
|
||||
("unknown", "unknown"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
Whitespace-only changes.
Executable
+117
@@ -0,0 +1,117 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from model_utils import Choices
|
||||
from polymorphic.base import PolymorphicModelBase
|
||||
from polymorphic.managers import PolymorphicManager
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from polymorphic.query import PolymorphicQuerySet
|
||||
from rules.contrib.models import RulesModelBaseMixin, RulesModelMixin
|
||||
|
||||
from outbo.apps.core.fields import (
|
||||
RemovalDateTimeField,
|
||||
CreationDateTimeField,
|
||||
ModificationDateTimeField,
|
||||
)
|
||||
from outbo.apps.core.utils import get_now
|
||||
|
||||
EmailLogStatus = Choices("sending", "sent", "queued", "invalid", "rejected", "failed", "unknown")
|
||||
|
||||
|
||||
class EmailLog(models.Model):
|
||||
sender = models.CharField(max_length=254, blank=False, null=False)
|
||||
receiver = models.CharField(max_length=254, blank=False, null=False)
|
||||
time = models.DateTimeField(blank=False, null=False)
|
||||
status = models.CharField(max_length=20, blank=False, null=False, choices=EmailLogStatus)
|
||||
|
||||
|
||||
if "anymail" in settings.INSTALLED_APPS:
|
||||
from anymail.signals import pre_send, post_send
|
||||
from anymail.message import AnymailStatus
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
def log_email_message(sender: str, receiver: str, time: datetime, status: str):
|
||||
try:
|
||||
EmailLog.objects.create(sender=sender, receiver=receiver, time=time, status=status)
|
||||
except:
|
||||
pass
|
||||
|
||||
@receiver(pre_send)
|
||||
def handle_pre_send(sender, message: EmailMessage, **kwargs):
|
||||
log_email_message(
|
||||
sender=str(message.from_email),
|
||||
receiver=", ".join(message.to),
|
||||
time=get_now(),
|
||||
status=EmailLogStatus.sending,
|
||||
)
|
||||
|
||||
@receiver(post_send)
|
||||
def handle_post_send(sender, message: EmailMessage, status: AnymailStatus, esp_name, **kwargs):
|
||||
for email, recipient_status in status.recipients.items():
|
||||
log_email_message(
|
||||
sender=str(message.from_email),
|
||||
receiver=email,
|
||||
time=get_now(),
|
||||
status=recipient_status.status or "failed",
|
||||
)
|
||||
|
||||
|
||||
class SoftDeletableQuerySet(models.QuerySet):
|
||||
def delete(self):
|
||||
return super().update(deleted_at=get_now())
|
||||
|
||||
def hard_delete(self):
|
||||
return super().delete()
|
||||
|
||||
|
||||
class SoftDeletableManager(models.Manager):
|
||||
queryset_class = SoftDeletableQuerySet
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.alive_only = kwargs.pop("alive_only", True)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.alive_only:
|
||||
return self.queryset_class(self.model).filter(deleted_at=None)
|
||||
return self.queryset_class(self.model)
|
||||
|
||||
|
||||
class TimestampableDeletableMixin(models.Model):
|
||||
objects = SoftDeletableManager()
|
||||
all_objects = SoftDeletableManager(alive_only=False)
|
||||
|
||||
created_at = CreationDateTimeField(_("Created at"))
|
||||
updated_at = ModificationDateTimeField(_("Updated at"))
|
||||
deleted_at = RemovalDateTimeField(_("Deleted at"))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def delete(self, using=None, **kwargs):
|
||||
self.deleted_at = get_now()
|
||||
# TODO: handle autodeletion of nested stuff
|
||||
self.save(using=using)
|
||||
|
||||
def __str__(self):
|
||||
return super().__str__() if self.deleted_at is None else f"{super().__str__()} (deleted)"
|
||||
|
||||
|
||||
class RulesPolymorphicModelBase(RulesModelBaseMixin, PolymorphicModelBase):
|
||||
pass
|
||||
|
||||
|
||||
class RulesPolymorphicModel(RulesModelMixin, PolymorphicModel, metaclass=RulesPolymorphicModelBase):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class SoftDeletablePolymorphicQuerySet(SoftDeletableQuerySet, PolymorphicQuerySet):
|
||||
pass
|
||||
|
||||
|
||||
class SoftDeletablePolymorphicManager(SoftDeletableManager, PolymorphicManager):
|
||||
queryset_class = SoftDeletablePolymorphicQuerySet
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
from djangorestframework_camel_case.parser import CamelCaseMultiPartParser
|
||||
from formencode.variabledecode import variable_decode
|
||||
from rest_framework import parsers
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
|
||||
|
||||
class NestedMultiPartParser(MultiPartParser):
|
||||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
result = super().parse(
|
||||
stream=stream, media_type=media_type, parser_context=parser_context
|
||||
)
|
||||
data = variable_decode(result.data)
|
||||
files = variable_decode(result.files)
|
||||
|
||||
return parsers.DataAndFiles(data, files)
|
||||
|
||||
|
||||
class CamelCaseNestedMultiPartParser(CamelCaseMultiPartParser):
|
||||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
result = super().parse(
|
||||
stream=stream, media_type=media_type, parser_context=parser_context
|
||||
)
|
||||
data = variable_decode(result.data)
|
||||
files = variable_decode(result.files)
|
||||
|
||||
return parsers.DataAndFiles(data, files)
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
from rules import predicate
|
||||
|
||||
|
||||
@predicate
|
||||
def is_agency(user):
|
||||
return user.is_authenticated and user.is_agency
|
||||
|
||||
|
||||
@predicate
|
||||
def is_client(user):
|
||||
return user.is_authenticated and user.is_client
|
||||
|
||||
|
||||
@predicate
|
||||
def is_supplier(user):
|
||||
return user.is_authenticated and user.is_supplier
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_polymorphic.serializers import (
|
||||
PolymorphicSerializer as BasePolymorphicSerializer,
|
||||
)
|
||||
|
||||
|
||||
class PolymorphicSerializer(BasePolymorphicSerializer):
|
||||
resource_type_field_name = "type"
|
||||
|
||||
def to_resource_type(self, model_or_instance):
|
||||
return model_or_instance._meta.verbose_name
|
||||
|
||||
|
||||
class EnumCharField(serializers.ChoiceField):
|
||||
default_error_messages = {"invalid_choice": _('"{input}" is not a valid choice.')}
|
||||
|
||||
def __init__(self, enum, **kwargs):
|
||||
self.enum = enum
|
||||
kwargs.setdefault("choices", [(item, item.value) for item in enum])
|
||||
super().__init__(**kwargs)
|
||||
self.enum = enum
|
||||
|
||||
def to_internal_value(self, value):
|
||||
value = super().to_representation(value)
|
||||
if not isinstance(value, self.enum):
|
||||
try:
|
||||
enum_name, enum_value = value.split(".", 1)
|
||||
if enum_name == self.enum.__name__:
|
||||
value = self.enum[enum_value]
|
||||
except ValueError:
|
||||
raise ValidationError(self.error_messages["invalid"], code="invalid")
|
||||
return value
|
||||
|
||||
def to_representation(self, value):
|
||||
if isinstance(value, self.enum):
|
||||
return str(value)
|
||||
return value
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
from storages.utils import setting
|
||||
|
||||
|
||||
class S3StaticStorage(S3Boto3Storage):
|
||||
bucket_name = setting("AWS_STORAGE_BUCKET_STATIC_NAME")
|
||||
default_acl = "public-read"
|
||||
querystring_auth = False
|
||||
|
||||
|
||||
class S3MediaStorage(S3Boto3Storage):
|
||||
bucket_name = setting("AWS_STORAGE_BUCKET_MEDIA_NAME")
|
||||
default_acl = None
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
|
||||
from outbo.apps.core.views import RootApiView
|
||||
from outbo.apps.products.views import ExportBriefToPDFApiView
|
||||
|
||||
urlpatterns = [
|
||||
path("", RootApiView.as_view(), name="root"),
|
||||
path("briefs/<int:pk>/export-to-pdf/", ExportBriefToPDFApiView.as_view(), name="export_brief_to_pdf"),
|
||||
]
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def get_now():
|
||||
return timezone.now()
|
||||
|
||||
|
||||
def get_current_date():
|
||||
return timezone.localtime(get_now()).date()
|
||||
|
||||
|
||||
def is_empty_set(tested_set):
|
||||
return tested_set == set()
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
from django.conf import settings
|
||||
from rest_framework import permissions, views, response
|
||||
|
||||
|
||||
class RootApiView(views.APIView):
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get(self, request):
|
||||
print(request)
|
||||
return response.Response({"version": settings.APP_VERSION})
|
||||
Executable
+160
@@ -0,0 +1,160 @@
|
||||
import six
|
||||
from django.db import transaction
|
||||
from django.db.models import Model
|
||||
from rest_framework import status, mixins
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from rest_framework_extensions.mixins import (
|
||||
NestedViewSetMixin as BaseNestedViewSetMixin,
|
||||
)
|
||||
from rest_framework_extensions.settings import extensions_api_settings
|
||||
from rules.contrib.rest_framework import (
|
||||
AutoPermissionViewSetMixin as BaseAutoPermissionViewSetMixin,
|
||||
)
|
||||
|
||||
|
||||
class AutoPermissionViewSetMixin(BaseAutoPermissionViewSetMixin):
|
||||
permission_type_map = {
|
||||
**BaseAutoPermissionViewSetMixin.permission_type_map,
|
||||
"metadata": None,
|
||||
}
|
||||
|
||||
|
||||
class CreateModelMixin:
|
||||
def create(self, request, *args, **kwargs):
|
||||
if isinstance(request.data, list):
|
||||
with transaction.atomic():
|
||||
response = []
|
||||
for item in request.data:
|
||||
write_serializer = self.get_serializer(data=item)
|
||||
write_serializer.is_valid(raise_exception=True)
|
||||
instance = self.perform_create(serializer=write_serializer)
|
||||
read_serializer = self.get_read_serializer(instance=instance)
|
||||
response.append(read_serializer.data)
|
||||
return Response(response, status=status.HTTP_201_CREATED)
|
||||
|
||||
else:
|
||||
write_serializer = self.get_serializer(data=request.data)
|
||||
write_serializer.is_valid(raise_exception=True)
|
||||
|
||||
instance = self.perform_create(serializer=write_serializer)
|
||||
|
||||
read_serializer = self.get_read_serializer(instance=instance)
|
||||
headers = self.get_success_headers(read_serializer.data)
|
||||
|
||||
return Response(
|
||||
read_serializer.data, status=status.HTTP_201_CREATED, headers=headers
|
||||
)
|
||||
|
||||
def perform_create(self, *, serializer):
|
||||
return serializer.save()
|
||||
|
||||
def get_success_headers(self, data):
|
||||
try:
|
||||
return {"Location": str(data[api_settings.URL_FIELD_NAME])}
|
||||
except (TypeError, KeyError):
|
||||
return {}
|
||||
|
||||
|
||||
class UpdateModelMixin:
|
||||
def update(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
write_serializer = self.get_serializer(
|
||||
instance, data=request.data, partial=False
|
||||
)
|
||||
write_serializer.is_valid(raise_exception=True)
|
||||
|
||||
instance = write_serializer.save()
|
||||
|
||||
if getattr(instance, "_prefetched_objects_cache", None):
|
||||
# If 'prefetch_related' has been applied to a queryset, we need to
|
||||
# forcibly invalidate the prefetch cache on the instance.
|
||||
instance._prefetched_objects_cache = {}
|
||||
|
||||
read_serializer = self.get_read_serializer(instance=instance)
|
||||
|
||||
return Response(read_serializer.data)
|
||||
|
||||
|
||||
class PartialUpdateModelMixin:
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
write_serializer = self.get_serializer(
|
||||
instance, data=request.data, partial=True
|
||||
)
|
||||
write_serializer.is_valid(raise_exception=True)
|
||||
|
||||
instance = write_serializer.save()
|
||||
|
||||
if getattr(instance, "_prefetched_objects_cache", None):
|
||||
# If 'prefetch_related' has been applied to a queryset, we need to
|
||||
# forcibly invalidate the prefetch cache on the instance.
|
||||
instance._prefetched_objects_cache = {}
|
||||
|
||||
read_serializer = self.get_read_serializer(instance=instance)
|
||||
|
||||
return Response(read_serializer.data)
|
||||
|
||||
|
||||
class RetrieveModelMixin(mixins.RetrieveModelMixin):
|
||||
pass
|
||||
|
||||
|
||||
class ListModelMixin(mixins.ListModelMixin):
|
||||
pass
|
||||
|
||||
|
||||
class DestroyModelMixin(mixins.DestroyModelMixin):
|
||||
pass
|
||||
|
||||
|
||||
class MultipleSerializerViewSet(GenericViewSet):
|
||||
serializer_class_set = None
|
||||
serializer_map = {
|
||||
"list": "List",
|
||||
"retrieve": "Retrieve",
|
||||
"create": "Create",
|
||||
"partial_update": "Update",
|
||||
}
|
||||
|
||||
def get_serializer_class(self):
|
||||
assert (
|
||||
self.serializer_class_set is not None
|
||||
), "serializer_class_set should be defined"
|
||||
|
||||
if self.action not in self.serializer_map:
|
||||
raise KeyError(
|
||||
f"""serializer_map does not contain serializer for "{self.action}" method"""
|
||||
)
|
||||
return getattr(self.serializer_class_set, self.serializer_map[self.action])
|
||||
|
||||
def get_read_serializer(self, *args, **kwargs):
|
||||
assert (
|
||||
self.serializer_class_set is not None
|
||||
), "serializer_class_set should be defined"
|
||||
if self.action != "create":
|
||||
if getattr(self.serializer_class_set, "Retrieve", None) is None:
|
||||
raise KeyError(
|
||||
f"""serializer_class_set does not contain "Retrieve" serializer"""
|
||||
)
|
||||
serializer_class = self.serializer_class_set.Retrieve
|
||||
else:
|
||||
if (
|
||||
getattr(self.serializer_class_set, "Retrieve", None) is None
|
||||
and getattr(self.serializer_class_set, "AfterCreate", None) is None
|
||||
):
|
||||
raise KeyError(
|
||||
f"""serializer_class_set does not contain "Retrieve" nor "AfterCreate" serializer and is used in "create" action."""
|
||||
)
|
||||
if getattr(self.serializer_class_set, "AfterCreate", None) is not None:
|
||||
serializer_class = self.serializer_class_set.AfterCreate
|
||||
else:
|
||||
serializer_class = self.serializer_class_set.Retrieve
|
||||
|
||||
kwargs["context"] = self.get_serializer_context()
|
||||
return serializer_class(*args, **kwargs)
|
||||
|
||||
|
||||
class NestedViewSetMixin(BaseNestedViewSetMixin):
|
||||
pass
|
||||
Reference in new issue
Block a user