added endpoints, filters, serializers and extend model Product, ProductType, ProductSubtype
This commit is contained in:
parent
e3616ee7f8
commit
37d745eeaa
|
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from main.models import Award
|
from main.models import Award
|
||||||
|
from product.models import Product
|
||||||
from review import models as review_models
|
from review import models as review_models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,13 +47,18 @@ class CommentInline(GenericTabularInline):
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class ProductInline(admin.TabularInline):
|
||||||
|
model = Product
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Establishment)
|
@admin.register(models.Establishment)
|
||||||
class EstablishmentAdmin(admin.ModelAdmin):
|
class EstablishmentAdmin(admin.ModelAdmin):
|
||||||
"""Establishment admin."""
|
"""Establishment admin."""
|
||||||
list_display = ['id', '__str__', 'image_tag', ]
|
list_display = ['id', '__str__', 'image_tag', ]
|
||||||
inlines = [
|
inlines = [
|
||||||
AwardInline, ContactPhoneInline, ContactEmailInline,
|
AwardInline, ContactPhoneInline, ContactEmailInline,
|
||||||
ReviewInline, CommentInline]
|
ReviewInline, CommentInline, ProductInline]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Position)
|
@admin.register(models.Position)
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
"""
|
"""
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wines(self):
|
||||||
|
"""Return list products with type wine"""
|
||||||
|
return self.products.wines()
|
||||||
|
|
||||||
|
|
||||||
class Position(BaseAttributes, TranslatedFieldsMixin):
|
class Position(BaseAttributes, TranslatedFieldsMixin):
|
||||||
"""Position model."""
|
"""Position model."""
|
||||||
|
|
|
||||||
18
apps/product/admin.py
Normal file
18
apps/product/admin.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
"""Product admin conf."""
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import Product, ProductType, ProductSubType
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Product)
|
||||||
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
|
"""Admin page for model Product."""
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ProductType)
|
||||||
|
class ProductTypeAdmin(admin.ModelAdmin):
|
||||||
|
"""Admin page for model ProductType."""
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ProductSubType)
|
||||||
|
class ProductSubTypeAdmin(admin.ModelAdmin):
|
||||||
|
"""Admin page for model ProductSubType."""
|
||||||
34
apps/product/filters.py
Normal file
34
apps/product/filters.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
"""Filters for app Product."""
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from product import models
|
||||||
|
|
||||||
|
|
||||||
|
class ProductListFilterSet(filters.FilterSet):
|
||||||
|
"""Product filter set."""
|
||||||
|
|
||||||
|
establishment_id = filters.NumberFilter()
|
||||||
|
type = filters.ChoiceFilter(method='by_type',
|
||||||
|
choices=models.ProductType.INDEX_NAME_TYPES)
|
||||||
|
subtype = filters.ChoiceFilter(method='by_subtype',
|
||||||
|
choices=models.ProductSubType.INDEX_NAME_TYPES)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Product
|
||||||
|
fields = [
|
||||||
|
'establishment_id',
|
||||||
|
'type',
|
||||||
|
'subtype',
|
||||||
|
]
|
||||||
|
|
||||||
|
def by_type(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_type(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def by_subtype(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_subtype(value)
|
||||||
|
return queryset
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Product app models."""
|
"""Product app models."""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from utils.models import (BaseAttributes, ProjectBaseMixin,
|
from utils.models import (BaseAttributes, ProjectBaseMixin,
|
||||||
|
|
@ -9,9 +10,23 @@ from utils.models import (BaseAttributes, ProjectBaseMixin,
|
||||||
class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
"""ProductType model."""
|
"""ProductType model."""
|
||||||
|
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
FOOD = 'food'
|
||||||
|
WINE = 'wine'
|
||||||
|
LIQUOR = 'liquor'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(FOOD, _('Food')),
|
||||||
|
(WINE, _('Wine')),
|
||||||
|
(LIQUOR, _('Liquor')),
|
||||||
|
)
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None,
|
name = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
||||||
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
verbose_name=_('Index name'))
|
verbose_name=_('Index name'))
|
||||||
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
||||||
|
|
||||||
|
|
@ -25,19 +40,35 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
|
class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
"""ProductSubtype model."""
|
"""ProductSubtype model."""
|
||||||
|
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
RUM = 'rum'
|
||||||
|
OTHER = 'other'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(RUM, _('Rum')),
|
||||||
|
(OTHER, _('Other')),
|
||||||
|
)
|
||||||
|
|
||||||
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
|
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
|
||||||
related_name='subtypes',
|
related_name='subtypes',
|
||||||
verbose_name=_('Product type'))
|
verbose_name=_('Product type'))
|
||||||
name = TJSONField(blank=True, null=True, default=None,
|
name = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
||||||
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
verbose_name=_('Index name'))
|
verbose_name=_('Index name'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
verbose_name = _('Product type')
|
verbose_name = _('Product subtype')
|
||||||
verbose_name_plural = _('Product types')
|
verbose_name_plural = _('Product subtypes')
|
||||||
|
|
||||||
|
def clean_fields(self, exclude=None):
|
||||||
|
if not self.product_type.use_subtypes:
|
||||||
|
raise ValidationError(_('Product type is not use subtypes.'))
|
||||||
|
|
||||||
|
|
||||||
class ProductManager(models.Manager):
|
class ProductManager(models.Manager):
|
||||||
|
|
@ -47,16 +78,33 @@ class ProductManager(models.Manager):
|
||||||
class ProductQuerySet(models.QuerySet):
|
class ProductQuerySet(models.QuerySet):
|
||||||
"""Product queryset."""
|
"""Product queryset."""
|
||||||
|
|
||||||
|
def with_base_related(self):
|
||||||
|
return self.select_related('country', 'product_type', 'establishment') \
|
||||||
|
.prefetch_related('product_type__subtypes')
|
||||||
|
|
||||||
def common(self):
|
def common(self):
|
||||||
return self.filter(category=self.model.COMMON)
|
return self.filter(category=self.model.COMMON)
|
||||||
|
|
||||||
def online(self):
|
def online(self):
|
||||||
return self.filter(category=self.model.ONLINE)
|
return self.filter(category=self.model.ONLINE)
|
||||||
|
|
||||||
|
def wines(self):
|
||||||
|
return self.filter(type__index_name=ProductType.WINE)
|
||||||
|
|
||||||
|
def by_type(self, type: str):
|
||||||
|
"""Filter by type."""
|
||||||
|
return self.filter(product_type__index_name=type)
|
||||||
|
|
||||||
|
def by_subtype(self, subtype: str):
|
||||||
|
"""Filter by subtype."""
|
||||||
|
return self.filter(subtypes__index_name=subtype)
|
||||||
|
|
||||||
|
|
||||||
class Product(TranslatedFieldsMixin, BaseAttributes):
|
class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
"""Product models."""
|
"""Product models."""
|
||||||
|
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
COMMON = 0
|
COMMON = 0
|
||||||
ONLINE = 1
|
ONLINE = 1
|
||||||
|
|
||||||
|
|
@ -75,10 +123,17 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
country = models.ForeignKey('location.Country', on_delete=models.PROTECT,
|
country = models.ForeignKey('location.Country', on_delete=models.PROTECT,
|
||||||
verbose_name=_('Country'))
|
verbose_name=_('Country'))
|
||||||
available = models.BooleanField(_('Available'), default=True)
|
available = models.BooleanField(_('Available'), default=True)
|
||||||
type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
|
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
|
||||||
related_name='products', verbose_name=_('Type'))
|
related_name='products', verbose_name=_('Type'))
|
||||||
subtypes = models.ManyToManyField(ProductSubType, related_name='products',
|
subtypes = models.ManyToManyField(ProductSubType, blank=True,
|
||||||
|
related_name='products',
|
||||||
verbose_name=_('Subtypes'))
|
verbose_name=_('Subtypes'))
|
||||||
|
establishment = models.ForeignKey('establishment.Establishment',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='products',
|
||||||
|
verbose_name=_('establishment'))
|
||||||
|
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('public mark'),)
|
||||||
|
|
||||||
objects = ProductManager.from_queryset(ProductQuerySet)()
|
objects = ProductManager.from_queryset(ProductQuerySet)()
|
||||||
|
|
||||||
|
|
@ -93,7 +148,7 @@ class OnlineProductManager(ProductManager):
|
||||||
"""Extended manger for OnlineProduct model."""
|
"""Extended manger for OnlineProduct model."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overrided get_queryset method."""
|
"""Overridden get_queryset method."""
|
||||||
return super().get_queryset().online()
|
return super().get_queryset().online()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .common import *
|
||||||
|
from .web import *
|
||||||
|
from .mobile import *
|
||||||
|
|
@ -1 +1,55 @@
|
||||||
|
"""Product app serializers."""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from utils.serializers import TranslatedField
|
||||||
|
from product.models import Product, ProductSubType, ProductType
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""ProductSubType base serializer"""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
index_name_display = serializers.CharField(source='get_index_name_display')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProductSubType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'index_name_display',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""ProductType base serializer"""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
index_name_display = serializers.CharField(source='get_index_name_display')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProductType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'index_name_display',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Product base serializer."""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
description_translated = TranslatedField()
|
||||||
|
category_display = serializers.CharField(source='get_category_display')
|
||||||
|
product_type = ProductTypeBaseSerializer()
|
||||||
|
subtypes = ProductSubTypeBaseSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = Product
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'category_display',
|
||||||
|
'description_translated',
|
||||||
|
'available',
|
||||||
|
'product_type',
|
||||||
|
'subtypes',
|
||||||
|
'public_mark',
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
"""Product url patterns."""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from product import views
|
||||||
|
|
||||||
|
app_name = 'product'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.ProductListView.as_view(), name='list')
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""Product web url patterns."""
|
||||||
|
from product.urls.common import urlpatterns as common_urlpatterns
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns.extend(common_urlpatterns)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .back import *
|
||||||
|
from .common import *
|
||||||
|
from .mobile import *
|
||||||
|
from .web import *
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
"""Product app views."""
|
||||||
|
from rest_framework import generics, permissions
|
||||||
|
from product.models import Product
|
||||||
|
from product import serializers
|
||||||
|
from product import filters
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBaseView(generics.GenericAPIView):
|
||||||
|
"""Product base view"""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method."""
|
||||||
|
return Product.objects.with_base_related()
|
||||||
|
|
||||||
|
|
||||||
|
class ProductListView(ProductBaseView, generics.ListAPIView):
|
||||||
|
"""List view for model Product."""
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
serializer_class = serializers.ProductBaseSerializer
|
||||||
|
filter_class = filters.ProductListFilterSet
|
||||||
|
|
@ -75,6 +75,7 @@ PROJECT_APPS = [
|
||||||
'favorites.apps.FavoritesConfig',
|
'favorites.apps.FavoritesConfig',
|
||||||
'rating.apps.RatingConfig',
|
'rating.apps.RatingConfig',
|
||||||
'tag.apps.TagConfig',
|
'tag.apps.TagConfig',
|
||||||
|
'product.apps.ProductConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTERNAL_APPS = [
|
EXTERNAL_APPS = [
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,5 @@ urlpatterns = [
|
||||||
path('comments/', include('comment.urls.web')),
|
path('comments/', include('comment.urls.web')),
|
||||||
path('favorites/', include('favorites.urls')),
|
path('favorites/', include('favorites.urls')),
|
||||||
path('timetables/', include('timetable.urls.web')),
|
path('timetables/', include('timetable.urls.web')),
|
||||||
|
path('products/', include('product.urls.web')),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user