feature/thumbnails #12

Scalone
mtyton scala 18 commity/ów z feature/thumbnails do main 2023-09-19 18:26:19 +00:00
15 zmienionych plików z 281 dodań i 38 usunięć

Wyświetl plik

@ -0,0 +1,21 @@
steps:
build:
image: docker:24.0.6
secrets: []
commands:
- docker compose -f artel/docker-compose-test.yml build
volumes:
- /var/run/docker.sock:/var/run/docker.sock
when:
event: pull_request
branch: ${CI_REPO_DEFAULT_BRANCH}
test:
image: docker:24.0.6
secrets: []
commands:
- docker compose -f artel/docker-compose-test.yml run --rm test_comfy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
when:
event: pull_request
branch: ${CI_REPO_DEFAULT_BRANCH}

Wyświetl plik

@ -0,0 +1,3 @@
from .celery import app as celery_app
__all__ = ('celery_app',)

Wyświetl plik

@ -0,0 +1,17 @@
import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'artel.settings.production')
app = Celery('artel')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django apps.
app.autodiscover_tasks()

Wyświetl plik

@ -65,6 +65,9 @@ INSTALLED_APPS = [
"django.contrib.staticfiles",
"rest_framework",
"phonenumber_field",
"django_celery_results",
"django_celery_beat",
"easy_thumbnails",
]
MIDDLEWARE = [
@ -214,6 +217,29 @@ EMAIL_PORT = os.environ.get('EMAIL_PORT', 587)
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'artel-sklep@tepewu.pl')
# CELERY settings
CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND")
CELERY_CACHE_BACKEND = os.environ.get("CELERY_CACHE_BACKEND")
CELERY_TIMEZONE = os.environ.get("CELERY_TIMEZONE")
CELERY_TASK_TRACK_STARTED = os.environ.get("CELERY_TASK_TRACK_STARTED")
CELERY_TASK_TIME_LIMIT = os.environ.get("CELERY_TASK_TIME_LIMIT")
# CELERY_RESULT_BACKEND_DB = f'db+mysql+pymysql://{os.environ.get("MYSQL_USER")}:{os.environ.get("MYSQL_PASSWORD")}@db/{os.environ.get("MYSQL_DATABASE")}'
CELERY_BROKER_URL = f'amqp://{os.environ.get("RABBITMQ_DEFAULT_USER")}:{os.environ.get("RABBITMQ_DEFAULT_PASS")}@rabbit//'
CELERY_TASK_RESULT_EXPIRES = os.environ.get("CELERY_TASK_RESULT_EXPIRES")
CELERY_ACCEPT_CONTENT = ['pickle'] #add this to your env
# EASY_THUMBNAILS settings
THUMBNAIL_DEFAULT_STORAGE = 'django.core.files.storage.FileSystemStorage'
THUMBNAIL_ALIASES = {
'': {
'image_40_60': {'size': (40, 60), 'crop': True},
'image_60_90': {'size': (60, 90), 'crop': True},
'image_80_120': {'size': (80, 120), 'crop': True},
'image_120_180': {'size': (120, 180), 'crop': True},
'image_160_240': {'size': (160, 240), 'crop': True},
},
}
LOGGING = {
"version": 1,

Wyświetl plik

@ -0,0 +1,12 @@
from artel.settings.base import *
# SECURITY WARNING: define the correct hosts in production!
ALLOWED_HOSTS = ["*"]
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = "smtp-server"
EMAIL_HOST_USER = None
EMAIL_HOST_PASSWORD = None
EMAIL_PORT = 1025
EMAIL_USE_TLS = False
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'mtyton@tepewu.pl')

Wyświetl plik

@ -0,0 +1,20 @@
from .base import *
# SECURITY WARNING: define the correct hosts in production!
ALLOWED_HOSTS = ["*"]
SECRET_KEY = "test"
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = "smtp-server"
EMAIL_HOST_USER = None
EMAIL_HOST_PASSWORD = None
EMAIL_PORT = 1025
EMAIL_USE_TLS = False
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'mtyton@tepewu.pl')
try:
from .local import *
except ImportError:
pass

Wyświetl plik

@ -0,0 +1,19 @@
import celery
import logging
from easy_thumbnails.files import generate_all_aliases
logger = logging.getLogger(__name__)
@celery.shared_task(name="generate_thumbnails")
def generate_thumbnails(model, pk, field):
try:
instance = model._default_manager.get(pk=pk)
fieldfile = getattr(instance, field)
generate_all_aliases(fieldfile, include_global=True)
return {"status": True}
except Exception as e:
logger.exception("An error occurred while generating thumbnails.")
return {"status": False, "error": str(e)}

Wyświetl plik

@ -4,14 +4,18 @@ services:
image: postgres
restart: always
environment:
- POSTGRES_ROOT_PASSWORD
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
volumes:
- ../postgres/:/var/lib/postgresql
env_file:
- .env
- POSTGRES_ROOT_PASSWORD=password
- POSTGRES_USER=comfy
- POSTGRES_PASSWORD=password
- POSTGRES_DB=comfy_shop
test_rabbit:
hostname: rabbit
image: rabbitmq:3.6.0
environment:
- RABBITMQ_DEFAULT_USER=rabbitmq
- RABBITMQ_DEFAULT_PASS=rabbitmq
test_comfy:
depends_on:
- test_db
@ -19,12 +23,38 @@ services:
dockerfile: Dockerfile.local
context: ./
user: "${UID}:${GID}"
volumes:
- ./:/app
environment:
- SECRET_KEY
- DATABASE_URL
env_file:
- .env
- SECRET_KEY=RandomKey
- DATABASE_URL=postgres://comfy:password@test_db/comfy_shop
- DJANGO_SETTINGS_MODULE=artel.settings.tests
- RABBITMQ_DEFAULT_USER=rabbitmq
- RABBITMQ_DEFAULT_PASS=rabbitmq
command:
python manage.py test --noinput
test_beat:
build:
context: .
dockerfile: Dockerfile
command: celery -A artel beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
environment:
- SECRET_KEY=RandomKey
- DATABASE_URL=postgres://comfy:password@test_db/comfy_shop
- DJANGO_SETTINGS_MODULE=artel.settings.tests
depends_on:
- test_comfy
- test_rabbit
test_worker:
build:
context: .
dockerfile: Dockerfile
command: celery -A artel worker -l info
environment:
- SECRET_KEY=RandomKey
- DATABASE_URL=postgres://comfy:password@test_db/comfy_shop
- DJANGO_SETTINGS_MODULE=artel.settings.tests
depends_on:
- test_comfy
- test_rabbit
- test_beat

Wyświetl plik

@ -1,10 +1,35 @@
version: "3.8"
services:
db:
image: postgres
restart: always
environment:
- POSTGRES_ROOT_PASSWORD
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
volumes:
- ../postgres/:/var/lib/postgresql
env_file:
- .env
rabbit:
hostname: rabbit
image: rabbitmq:3.6.0
environment:
- RABBITMQ_DEFAULT_USER
- RABBITMQ_DEFAULT_PASS
ports:
- "5672:5672" # We forward this port because it's useful for debugging
- "15672:15672" # Here, we can access RabbitMQ management plugin
smtp-server:
image: mailhog/mailhog
ports:
- "1025:1025"
- "8025:8025"
- "8025:8025"
comfy:
restart: always
depends_on:
@ -26,18 +51,40 @@ services:
- .env
stdin_open: true
tty: true
db:
image: postgres
restart: always
environment:
- POSTGRES_ROOT_PASSWORD
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
beat:
build:
context: .
dockerfile: Dockerfile
command: celery -A artel beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
volumes:
- db:/var/lib/postgresql/data
- ./:/app
env_file:
- .env
environment:
- SECRET_KEY
- DATABASE_URL
depends_on:
- comfy
- rabbit
worker:
build:
context: .
dockerfile: Dockerfile
command: celery -A artel worker -l info
volumes:
- ./:/app
- ./media:/app/media
env_file:
- .env
environment:
- SECRET_KEY
- DATABASE_URL
depends_on:
- comfy
- rabbit
- beat
adminer:
image: adminer
@ -49,3 +96,4 @@ services:
volumes:
media:
db:
rabbitmq_data:

Wyświetl plik

@ -8,6 +8,10 @@ phonenumbers==8.13.13
django-phonenumber-field==7.1.0
factory-boy==3.2.1
pdfkit==1.0.0
celery==5.3.1
django-celery-beat==2.5.0
django-celery-results==2.5.1
easy_thumbnails==2.8.5
num2words==0.5.12
sentry-sdk==1.28.0
pandas==2.0.3

Wyświetl plik

@ -0,0 +1,23 @@
# Generated by Django 4.1.10 on 2023-09-09 16:42
from django.db import migrations
import easy_thumbnails.fields
class Migration(migrations.Migration):
dependencies = [
("store", "0013_producttemplateparam_alter_product_params_and_more"),
]
operations = [
migrations.AlterField(
model_name="productimage",
name="image",
field=easy_thumbnails.fields.ThumbnailerImageField(upload_to=""),
),
migrations.AlterField(
model_name="producttemplateimage",
name="image",
field=easy_thumbnails.fields.ThumbnailerImageField(upload_to=""),
)
]

Wyświetl plik

@ -23,6 +23,7 @@ from django.template import (
from django.core.exceptions import ValidationError
from django.db.models.signals import m2m_changed
from django.forms import CheckboxSelectMultiple
from django.dispatch import receiver
from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalKey
@ -35,18 +36,21 @@ from wagtail import fields as wagtail_fields
from taggit.managers import TaggableManager
from phonenumber_field.modelfields import PhoneNumberField
from num2words import num2words
from easy_thumbnails.fields import ThumbnailerImageField
from easy_thumbnails.signals import saved_file
from mailings.models import (
OutgoingEmail,
Attachment
)
from artel.tasks import generate_thumbnails
logger = logging.getLogger(__name__)
class BaseImageModel(models.Model):
image = models.ImageField()
image = ThumbnailerImageField()
is_main = models.BooleanField(default=False)
class Meta:
@ -129,7 +133,7 @@ class ProductTemplateImage(BaseImageModel):
template = ParentalKey(
ProductTemplate, on_delete=models.CASCADE, related_name="template_images"
)
image = models.ImageField()
image = ThumbnailerImageField()
is_main = models.BooleanField(default=False)
@ -532,3 +536,11 @@ class OrderDocument(models.Model):
template = Template(content)
content = template.render(context)
return pdfkit.from_string(content, False)
@receiver(saved_file)
def generate_thumbnails_async(sender, fieldfile, **kwargs):
generate_thumbnails.delay(
model=sender, pk=fieldfile.instance.pk,
field=fieldfile.field.name
)

Wyświetl plik

@ -1,5 +1,7 @@
import logging
import celery
from django.conf import settings
from easy_thumbnails.files import generate_all_aliases
from mailings.models import OutgoingEmail
from store.models import Product
@ -8,8 +10,8 @@ from store.admin import ProductAdmin
logger = logging.getLogger(__name__)
# TODO - those should be modified to be celery tasks
@celery.shared_task(name="send_produt_request_email")
def send_produt_request_email(variant_pk: int):
try:
variant = Product.objects.get(pk=variant_pk)

Wyświetl plik

@ -1,15 +1,21 @@
{% load static %}
{% load thumbnail %}
<div class="card h-100" >
<div class="card-header text-truncate">{{item.title}}</div>
<div class="card-body p-0">
<img src="{{item.main_image.image.url}}"
class="img-fluid img-thumbnail rounded mx-auto d-block mt-2 mb-2" style="width: 13rem; height: 15rem;" alt="{{item.title}}">
<div class="card-header text-truncate">{{item.title}}</div>
<div class="card-body p-0">
<img src="{{ item.main_image.image|thumbnail_url:'image_40_60' }}" class="d-block d-sm-none img-fluid rounded mx-auto" alt="{{item.title}}">
<div class="card-footer row d-flex m-0">
<div class="col text-center">
<img src="{{ item.main_image.image|thumbnail_url:'image_60_90' }}" class="d-none d-sm-block d-md-none img-fluid rounded mx-auto" alt="{{item.title}}">
<img src="{{ item.main_image.image|thumbnail_url:'image_80_120' }}" class="d-none d-md-block d-lg-none img-fluid rounded mx-auto" alt="{{item.title}}">
<img src="{{ item.main_image.image|thumbnail_url:'image_120_180' }}" class="d-none d-lg-block d-xl-none img-fluid rounded mx-auto" alt="{{item.title}}">
<img src="{{ item.main_image.image|thumbnail_url:'image_160_240' }}" class="d-none d-xl-block img-fluid rounded mx-auto" alt="{{item.title}}">
</div>
<div class="card-footer row d-flex m-0">
<div class="col text-center">
<a href="{% url 'product-configure' item.id %}" class="btn btn-primary">Konfiguruj</a>
</div>
</div>
</div>
</div>
</div>

Wyświetl plik

@ -53,7 +53,7 @@ class CartView(TemplateView):
class CartActionView(ViewSet):
# TODO - test this, currently not in use
# NOTE - currently not in use
@action(detail=False, methods=["get"], url_path="list-products")
def list_products(self, request):
# get cart items
@ -144,7 +144,7 @@ class ConfigureProductSummaryView(View):
def post(self, request, variant_pk: int, *args, **kwargs):
# Here just send the email with product request
variant = Product.objects.get(pk=variant_pk)
send_produt_request_email(variant.pk)
send_produt_request_email.apply_async(args=[variant.pk])
messages.success(request, "Zapytanie o produkt zostało wysłane")
context = self.get_context_data(variant_pk)
return HttpResponseRedirect(context["store_url"])