Skip to content

Commit

Permalink
Added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ZuluPro committed Apr 22, 2016
1 parent 0d3d1f1 commit aa6a71b
Show file tree
Hide file tree
Showing 16 changed files with 258 additions and 57 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
recursive-include favicon * *.css *.html *.xml
include requirements*.txt
9 changes: 9 additions & 0 deletions favicon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
Django app for generate favicon in multiple format and write easily dedicated
HTML tags.
"""
VERSION = (0, 1)
__version__ = '.'.join([str(i) for i in VERSION])
__author__ = 'Anthony Monthe (ZuluPro)'
__email__ = '[email protected]'
__url__ = 'https://github.com/ZuluPro/favicon'
45 changes: 45 additions & 0 deletions favicon/generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from django.utils.six import BytesIO
from django.template.loader import get_template
from django.core.files import File
from PIL import Image

ICO_SIZES = [(16, 16), (32, 32), (48, 48), (64, 64)]
PNG_SIZES = (32, 57, 76, 96, 120, 128, 144, 152, 180, 195, 196)
WINDOWS_PNG_SIZES = (
((128, 128), 'smalltile.png'),
((270, 270), 'mediumtile.png'),
((558, 270), 'widetile.png'),
((558, 558), 'largetile.png'),
)


def generate(source_file, storage):
def write_file(output_file, name):
content = File(output_file, name)
storage._save(content, name)
# Save ICO
img = Image.open(source_file)
output_file = BytesIO()
img.save(fp=output_file, format='ICO', sizes=ICO_SIZES)
write_file(output_file, 'favicon.ico')
# Save PNG
for size in PNG_SIZES:
img = Image.open(source_file)
output_file = BytesIO()
output_name = 'favicon-%s.png' % size
img.thumbnail(size=(size, size), resample=Image.ANTIALIAS)
img.save(output_file, format='PNG')
write_file(output_file, output_name)
for size, output_name in WINDOWS_PNG_SIZES:
img = Image.open(source_file)
output_file = BytesIO()
img.thumbnail(size=size, resample=Image.ANTIALIAS)
img.save(output_file, format='PNG')
write_file(output_file, output_name)
# Create ieconfig.xml
output_name = 'ieconfig.xml'
output_file = BytesIO()
template = get_template('favicon/ieconfig.xml')
output_content = template.render({'tile_color': 'FFFFFF'})
output_file.write(output_content)
write_file(output_file, 'ieconfig.xml')
57 changes: 7 additions & 50 deletions favicon/management/commands/generate_favicon.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,18 @@
import os

from django.core.management.base import BaseCommand, CommandError
from django.template.loader import get_template
from django.core.files import File
from django.core.files.storage import get_storage_class
from django.utils.six import BytesIO

from PIL import Image
from favicon import settings

PNG_SIZE = (32, 57, 76, 96, 120, 128, 144, 152, 180, 195, 196)
WINDOWS_PNG_SIZE = (
([128, 128], 'smalltile.png'),
([270, 270], 'mediumtile.png'),
([558, 270], 'widetile.png'),
([558, 558], 'largetile.png'),
)
from favicon.generators import generate


class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('filename', nargs=1, type=str,
parser.add_argument('source_file', nargs=1, type=str,
help="Input file used to generate favicons")

def write_file(self, output_file, name):
storage = get_storage_class(settings.STORAGE)(**settings.STORAGE_OPTIONS)
content = File(output_file, name)
storage.save(content)

def handle(self, *args, **options):
filename = options['filename']
if not os.path.exists(filename):
raise CommandError("File '%s' does not exist." % filename)
# Set storage
# Save ICO
img = Image.open(filename)
output_file = BytesIO()
img.save(fp=output_file, sizes=[(16, 16), (32, 32), (48, 48), (64, 64)])
self.write(output_file, 'favicon.ico')
# Save PNG
for size in PNG_SIZE:
img = Image.open(filename)
output_file = BytesIO()
output_name = 'favicon-%s.png' % size
img.thumbnail(size=(size, size), resample=Image.ANTIALIAS)
img.save(output_file)
self.write(output_file, output_name)
for size, output_name in PNG_SIZE:
img = Image.open(filename)
output_file = BytesIO()
img.thumbnail(size=size, resample=Image.ANTIALIAS)
img.save(output_file)
self.write(output_file, output_name)
# Create ieconfig.xml
output_name = 'ieconfig.xml'
output_file = BytesIO()
template = get_template('favicon/ieconfig.xml')
output_content = template.render({'tile_color': 'FFFFFF'})
output_file.write(output_content)
self.write(output_file, 'ieconfig.xml')
source_file = options['source_file'][0]
if not os.path.exists(source_file):
raise CommandError("File '%s' does not exist." % source_file)
storage = get_storage_class(settings.STORAGE)(**settings.STORAGE_OPTIONS)
generate(source_file, storage)
8 changes: 4 additions & 4 deletions favicon/templates/favicon/ieconfig.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="{% static_url "smalltile.png" %}"/>
<square150x150logo src="{% static_url "mediumtile.png" %}"/>
<wide310x150logo src="{% static_url "widetile.png" %}"/>
<square310x310logo src="{% static_url "largetile.png" %}"/>
<square70x70logo src="{% static "smalltile.png" %}"/>
<square150x150logo src="{% static "mediumtile.png" %}"/>
<wide310x150logo src="{% static "widetile.png" %}"/>
<square310x310logo src="{% static "largetile.png" %}"/>
<TileColor>#{{ tile_color }}</TileColor>
</tile>
</msapplication>
Expand Down
3 changes: 0 additions & 3 deletions favicon/tests.py

This file was deleted.

Empty file added favicon/tests/__init__.py
Empty file.
Binary file added favicon/tests/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions favicon/tests/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
SECRET_KEY = '&qaeg(mBecauseitsmandatoryv@@n$if67ba-4e9&kk+j$$c+'

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
},
}

INSTALLED_APPS = [
'favicon',
]

STATIC_URL = '/static/'
FAVICON_STORAGE = 'favicon.tests.utils.FakeStorage'
24 changes: 24 additions & 0 deletions favicon/tests/test_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.test import TestCase
from django.core.management import execute_from_command_line
from favicon.management.commands.generate_favicon import Command
from favicon.tests.utils import HANDLED_FILES, BASE_IMG, EXPECTED_FILES


class GenerateFaviconCommandTest(TestCase):
def setUp(self):
self.command = Command()

def tearDown(self):
HANDLED_FILES.clean()

def test_execute_from_command_line(self):
execute_from_command_line(['', 'generate_favicon', BASE_IMG])
for name, content in HANDLED_FILES['written_files'].items():
self.assertIn(name, EXPECTED_FILES)
self.assertTrue(content.size)

def test_handle(self):
self.command.handle(source_file=[BASE_IMG])
for name, content in HANDLED_FILES['written_files'].items():
self.assertIn(name, EXPECTED_FILES)
self.assertTrue(content.size)
46 changes: 46 additions & 0 deletions favicon/tests/test_generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import re
from django.test import TestCase
from PIL import Image
from favicon.tests.utils import HANDLED_FILES, BASE_IMG, EXPECTED_FILES,\
FakeStorage
from favicon.generators import generate, PNG_SIZES, WINDOWS_PNG_SIZES

SRC_REG = re.compile(r'src="/static/([^"]*)"')


class GenerateTest(TestCase):
def setUp(self):
self.storage = FakeStorage()

def tearDown(self):
HANDLED_FILES.clean()

def test_generate(self):
generate(BASE_IMG, self.storage)
for name, content in HANDLED_FILES['written_files'].items():
self.assertIn(name, EXPECTED_FILES)
self.assertTrue(content.size)
# Test ICO file
ico = self.storage._open('favicon.ico')
self.assertEqual(Image.open(ico).format, 'ICO')
# Test PNG
for size in PNG_SIZES:
name = 'favicon-%d.png' % size
self.assertTrue(self.storage.exists(name))
png = self.storage._open(name)
img = Image.open(png)
self.assertEqual(img.format, 'PNG')
self.assertEqual(img.size, (size, size))
# Test Windows PNG
for size, name in WINDOWS_PNG_SIZES:
self.assertTrue(self.storage.exists(name))
png = self.storage._open(name)
img = Image.open(png)
self.assertEqual(img.format, 'PNG')
if size[0] != size[1] or size[0] > 440:
continue
self.assertEqual(img.size, size)
# Test ieconfig.xml
ieconfig = self.storage._open('ieconfig.xml').read()
for name in SRC_REG.findall(ieconfig):
self.assertTrue(self.storage.exists(name))
41 changes: 41 additions & 0 deletions favicon/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os
from django.core.files.storage import Storage

TEST_DIR = os.path.dirname(__file__)
BASE_IMG = os.path.join(TEST_DIR, 'logo.png')

EXPECTED_FILES = (
'favicon.ico',
'ieconfig.xml',
'smalltile.png', 'mediumtile.png', 'largetile.png', 'widetile.png',
'favicon-32.png', 'favicon-57.png', 'favicon-76.png', 'favicon-96.png',
'favicon-120.png', 'favicon-128.png', 'favicon-144.png', 'favicon-152.png',
'favicon-180.png', 'favicon-195.png', 'favicon-196.png',
)


class handled_files(dict):
"""
Dict for gather information about fake storage and clean between tests.
You should use the constant instance ``HANDLED_FILES`` and clean it
before tests.
"""
def __init__(self):
super(handled_files, self).__init__()
self.clean()

def clean(self):
self['written_files'] = {}
HANDLED_FILES = handled_files()


class FakeStorage(Storage):
def _save(self, content, name=None):
HANDLED_FILES['written_files'][name] = content

def _open(self, name):
HANDLED_FILES['written_files'][name].seek(0)
return HANDLED_FILES['written_files'][name]

def exists(self, name):
return name in HANDLED_FILES['written_files'].keys()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pillow
18 changes: 18 additions & 0 deletions runtests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env python
import os
import sys

import django
from django.conf import settings
from django.test.utils import get_runner

if __name__ == "__main__":
os.environ['DJANGO_SETTINGS_MODULE'] = 'favicon.tests.settings'
if len(sys.argv) == 1:
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["favicon.tests"])
sys.exit(bool(failures))
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
max-line-length = 99
exclude = tests,settings
44 changes: 44 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
import favicon


def read_file(name):
with open(name) as fd:
return fd.read()

keywords = ['django', 'web', 'favicon', 'html']

setup(
name='django-super-favicon',
version=favicon.__version__,
description=favicon.__doc__,
author=favicon.__author__,
author_email=favicon.__email__,
install_requires=read_file('requirements.txt'),
license='BSD',
url=favicon.__url__,
keywords=keywords,
packages=find_packages(exclude=[]),
# test_suite='tests.runtests.main',
# tests_require=read_file('requirements-test.txt'),
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Environment :: Console',
'Framework :: Django',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
],
)

0 comments on commit aa6a71b

Please sign in to comment.