Skip to content

Commit

Permalink
Add the product and product images models
Browse files Browse the repository at this point in the history
  • Loading branch information
Zercos committed Sep 26, 2020
1 parent 564b6b7 commit 0aae37b
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 27 deletions.
12 changes: 4 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.782
hooks:
- id: mypy
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
5 changes: 3 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ factory-boy = "*"
[packages]
django = "==3.0.7"
psycopg2 = "==2.8.5"
django-environ = "*"
django-extensions = "==3.0.2"
pydotplus = "*"
ipython = "*"
werkzeug = "*"
pre-commit = "*"
django-registration = "==3.1"
pillow = "*"
ipython = "*"
django-environ = "*"

[requires]
python_version = "3.8"
60 changes: 47 additions & 13 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
# Application definition

INSTALLED_APPS = [
'user',
'main',
'main.apps.MainConfig',
'user.apps.UserConfig',
'django_registration',
'django.contrib.admin',
'django.contrib.auth',
Expand Down
5 changes: 4 additions & 1 deletion main/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from django.contrib import admin

# Register your models here.
from main.models import Product, ProductImage

admin.site.register(Product)
admin.site.register(ProductImage)
3 changes: 3 additions & 0 deletions main/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@

class MainConfig(AppConfig):
name = 'main'

def ready(self):
import main.signals
Binary file added main/fixtures/cat.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions main/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.0.7 on 2020-09-26 15:32

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Product',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=60, verbose_name='Name')),
('description', models.CharField(blank=True, max_length=255, null=True, verbose_name='Description')),
('site', models.CharField(max_length=60, verbose_name='Site')),
('active', models.BooleanField(default=True, verbose_name='Active')),
('product_code', models.CharField(blank=True, max_length=60, null=True, verbose_name='Product code')),
('date_updated', models.DateTimeField(auto_now=True)),
('date_created', models.DateTimeField(auto_now_add=True)),
],
),
]
23 changes: 23 additions & 0 deletions main/migrations/0002_productimage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.0.7 on 2020-09-26 16:34

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('main', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='ProductImage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='product-images')),
('thumbnail', models.ImageField(null=True, upload_to='product-thumbnails')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Product')),
],
),
]
23 changes: 22 additions & 1 deletion main/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
from django.db import models

# Create your models here.

class ProductManager(models.Manager):
def active(self):
return self.filter(active=True)


class Product(models.Model):
name = models.CharField('Name', null=False, blank=False, max_length=60)
description = models.CharField('Description', null=True, blank=True, max_length=255)
site = models.CharField('Site', null=False, blank=False, max_length=60)
active = models.BooleanField('Active', null=False, default=True)
product_code = models.CharField('Product code', null=True, blank=True, max_length=60)
date_updated = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True, null=False)

objects = ProductManager()


class ProductImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
image = models.ImageField(upload_to='product-images')
thumbnail = models.ImageField(upload_to='product-thumbnails', null=True, blank=True)
32 changes: 32 additions & 0 deletions main/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import logging
from io import BytesIO

from PIL import Image
from django.core.files.base import ContentFile
from django.db.models.signals import pre_save
from django.dispatch import receiver

from main.models import ProductImage

THUMBNAIL_SIZE = (300, 300)
logger = logging.getLogger(__name__)


@receiver(pre_save, sender=ProductImage)
def generate_thumbnail(sender, instance, **kwargs):
logger.info('Generating thumbnail for product %d', instance.product.id)
image = Image.open(instance.image)
image = image.convert('RGB')
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)

temp_thumb = BytesIO()
image.save(temp_thumb, 'JPEG')
temp_thumb.seek(0)

# set save=False, otherwise it will run in an infinite loop
instance.thumbnail.save(
instance.image.name,
ContentFile(temp_thumb.read()),
save=False,
)
temp_thumb.close()
13 changes: 13 additions & 0 deletions main/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import factory.fuzzy

from main import models


class ProductFactory(factory.django.DjangoModelFactory):
name = factory.Sequence(lambda x: f'Product {x}')
description = factory.fuzzy.FuzzyText()
site = factory.faker.Faker('url')
product_code = factory.Sequence(lambda x: f'product_{x}')

class Meta:
model = models.Product
11 changes: 11 additions & 0 deletions main/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.test import TestCase

from main.models import Product
from main.tests.factories import ProductFactory


class TestModels(TestCase):
def test_product_manager(self):
ProductFactory.create_batch(2, active=True)
ProductFactory.create(active=False)
self.assertEqual(2, len(Product.objects.active()))
22 changes: 22 additions & 0 deletions main/tests/test_signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from decimal import Decimal

from django.core.files.images import ImageFile
from django.test import TestCase

from main import models
from main.tests.factories import ProductFactory


class TestSignal(TestCase):
def test_thumbnails_are_generated_on_save(self):
product = ProductFactory()

with open('main/fixtures/cat.jpeg', 'rb') as f:
image = models.ProductImage(product=product, image=ImageFile(f, name='cat.jpeg'))
image.save()
image.refresh_from_db()
with open('media/product-thumbnails/cat.jpeg', 'rb') as f:
expected_content = f.read()
assert image.thumbnail.read() == expected_content
image.thumbnail.delete(save=False)
image.image.delete(save=False)

0 comments on commit 0aae37b

Please sign in to comment.