├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── image_optimizer ├── __init__.py ├── fields.py ├── settings.py └── utils.py ├── image_optimizer_demo ├── app_demo │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── .gitignore │ │ └── __init__.py │ └── models.py ├── image_optimizer_demo │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py ├── push.sh ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | build/ 3 | dist/ 4 | *.egg-info/ 5 | *.pypirc 6 | *.pyc 7 | *backup* 8 | db.sqlite3 9 | .vscode 10 | image_optimizer_demo/media/* 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 agus makmun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include requirements.txt 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-image-optimizer |pypi version| 2 | --------------------------------------- 3 | 4 | .. |pypi version| 5 | image:: https://img.shields.io/pypi/v/django-image-optimizer.svg 6 | :target: https://pypi.python.org/pypi/django-image-optimizer 7 | 8 | .. image:: https://img.shields.io/badge/license-MIT-blue.svg 9 | :target: https://raw.githubusercontent.com/agusmakmun/django-image-optimizer/master/LICENSE 10 | 11 | .. image:: https://img.shields.io/pypi/pyversions/django-image-optimizer.svg 12 | :target: https://pypi.python.org/pypi/django-image-optimizer 13 | 14 | .. image:: https://img.shields.io/badge/Django-1.8%20%3E=%203.0-green.svg 15 | :target: https://www.djangoproject.com 16 | 17 | 18 | Django Image Optimizer is a simple Django library that allows optimization 19 | of images by using `TinyPNG `_ or `Pillow `_. 20 | 21 | 22 | Installation 23 | ------------------------------ 24 | 25 | Django Image Optimizer is available directly from `PyPI `_: 26 | 27 | 1. Installing the package. 28 | 29 | :: 30 | 31 | $ pip install django-image-optimizer 32 | 33 | 34 | 2. Don't forget to add ``'image_optimizer'`` to your ``'INSTALLED_APPS'``. 35 | 36 | :: 37 | 38 | # settings.py 39 | INSTALLED_APPS = [ 40 | .... 41 | 'image_optimizer', 42 | ] 43 | 44 | 45 | 3. You have the option to use either TinyPNG or Pillow for optimizing images. 46 | Inform ``optimized_image`` which one you want to use by setting the following 47 | 48 | :: 49 | 50 | # To use Pillow 51 | OPTIMIZED_IMAGE_METHOD = 'pillow' 52 | # To use TinyPNG 53 | OPTIMIZED_IMAGE_METHOD = 'tinypng' 54 | 55 | Any other string that is set for this setting will mean that optimization does 56 | not occur. If you are unsure of whether you would like to use TinyPNG or Pillow, 57 | feel free to consult the documentation of each. 58 | 59 | If you choose to use TinyPNG, you will need to get an API key from 60 | TinyPNG. Visit https://tinypng.com/developers for more details on getting an 61 | API key. Once you have done so, add the following setting to your settings 62 | file. Note: it is a good idea to keep this secret 63 | 64 | :: 65 | 66 | TINYPNG_KEY = 'your-key' 67 | 68 | 69 | 4. You may use the ``OptimizedImageField`` by importing it 70 | 71 | :: 72 | 73 | from django.db import models 74 | 75 | from image_optimizer.fields import OptimizedImageField 76 | 77 | 78 | class MyModel(models.Model): 79 | ... 80 | image = OptimizedImageField() 81 | 82 | 83 | class MyModel2(models.Model): 84 | """ 85 | If you using OPTIMIZED_IMAGE_METHOD = 'pillow' 86 | You can use this optional arguments. 87 | 88 | This model represents a MyModel2 with a few 89 | fields including a `image` field which is an OptimizedImageField 90 | instance with `optimized_image_output_size` and 91 | `optimized_image_resize_method` arguments set. 92 | 93 | This means that image would be a resized 94 | version of the source image, meant to keep a given screen resolution, 95 | in this case (400, 300) pixels. 96 | """ 97 | image = OptimizedImageField( 98 | upload_to="uploads/%Y/%m/%d", 99 | optimized_image_output_size=(400, 300), 100 | optimized_image_resize_method="cover" # "crop", "cover", "contain", "width", "height", "thumbnail" or None 101 | ) 102 | 103 | 104 | and saving images into it, the same way you would to a Django ``ImageField``. 105 | The optimized image will be saved into the ``url`` field in place of the 106 | unoptimized image. 107 | 108 | 109 | 5. Or you can directly use the ``image_optimizer`` function from utils. 110 | 111 | :: 112 | 113 | from image_optimizer.utils import image_optimizer 114 | 115 | 116 | def post_image(request): 117 | image_data = request.FILES.get('image') 118 | image_data = image_optimizer(image_data=image_data, 119 | output_size=(400, 300), 120 | resize_method='cover') 121 | .... 122 | 123 | 124 | **P.S:** 125 | 126 | Note about TinyPNG API keys: If you obtain the free TinyPNG API token, you are limited to 500 127 | image optimizations per month, so this function may fail if you have a 128 | lot of images. You may either obtain a paid API key, or wait until next month. 129 | 130 | This project forked from: https://github.com/dchukhin/django_optimized_image 131 | -------------------------------------------------------------------------------- /image_optimizer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/django-image-optimizer/323659dc81cbdf2c068789550e81fa4b97ac2bc9/image_optimizer/__init__.py -------------------------------------------------------------------------------- /image_optimizer/fields.py: -------------------------------------------------------------------------------- 1 | from django.db.models import ImageField 2 | from .utils import image_optimizer 3 | 4 | 5 | class OptimizedImageField(ImageField): 6 | """An ImageField that gets optimized on save() using tinyPNG.""" 7 | 8 | def save_form_data(self, instance, data): 9 | """Remove the OptimizedNotOptimized object on clearing the image.""" 10 | # Are we updating an image? 11 | updating_image = ( 12 | True if data and getattr(instance, self.name) != data else False 13 | ) 14 | 15 | if updating_image: 16 | data = image_optimizer( 17 | data, 18 | self.optimized_image_output_size, 19 | self.optimized_image_resize_method, 20 | ) 21 | 22 | super().save_form_data(instance, data) 23 | 24 | def __init__( 25 | self, 26 | optimized_image_output_size=None, 27 | optimized_image_resize_method=None, 28 | *args, 29 | **kwargs 30 | ): 31 | """ 32 | Initialize OptimizedImageField instance. 33 | 34 | set up the `optimized_image_output_size` and 35 | `optimized_image_resize_method` arguments for the current 36 | `OptimizedImageField` instance. 37 | """ 38 | # Set the optimized_image_output_size specified on your 39 | # OptimizedImageField model instances 40 | self.optimized_image_output_size = optimized_image_output_size 41 | 42 | # Set the optimized_image_resize_method specified on your 43 | # OptimizedImageField model instances 44 | self.optimized_image_resize_method = optimized_image_resize_method 45 | 46 | super().__init__(**kwargs) 47 | 48 | def deconstruct(self): 49 | """ 50 | Deconstruct method. 51 | 52 | deconstruct the field, allowing us to handle the field data, useful 53 | in cases where you want to add optional arguments to your custom 54 | field but you need to exclude them from migrations. 55 | """ 56 | name, path, args, kwargs = super().deconstruct() 57 | 58 | if kwargs.get("optimized_image_output_size"): 59 | del kwargs["optimized_image_output_size"] 60 | 61 | if kwargs.get("optimized_image_resize_method"): 62 | del kwargs["optimized_image_resize_method"] 63 | 64 | return name, path, args, kwargs 65 | -------------------------------------------------------------------------------- /image_optimizer/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | OPTIMIZED_IMAGE_METHOD = getattr(settings, "OPTIMIZED_IMAGE_METHOD", "pillow") 4 | TINYPNG_KEY = getattr(settings, "TINYPNG_KEY", None) 5 | -------------------------------------------------------------------------------- /image_optimizer/utils.py: -------------------------------------------------------------------------------- 1 | import tinify 2 | import logging 3 | import requests 4 | from io import BytesIO 5 | from PIL import Image 6 | from resizeimage import resizeimage 7 | from uuid import uuid4 8 | 9 | from .settings import OPTIMIZED_IMAGE_METHOD, TINYPNG_KEY 10 | 11 | 12 | BACKGROUND_TRANSPARENT = (255, 255, 255, 0) 13 | 14 | 15 | def get_file_name(image_data): 16 | return image_data.name 17 | 18 | 19 | def get_file_extension(file_name): 20 | extension = None 21 | # Get image file extension 22 | if file_name.split(".")[-1].lower() != "jpg": 23 | extension = file_name.split(".")[-1].upper() 24 | else: 25 | extension = "JPEG" 26 | return extension 27 | 28 | 29 | def get_image_extension(image): 30 | return image.format 31 | 32 | 33 | def image_optimizer(image_data, output_size=None, resize_method=None): 34 | """ 35 | Optimize an image that has not been saved to a file. 36 | :param `image_data` is image data, e.g from request.FILES['image'] 37 | :param `output_size` is float pixel scale of image (width, height) or None, for example: (400, 300) # noqa: E501 38 | :param `resize_method` is string resize method, choices are: 39 | None or resizeimage.resize() method argument values, 40 | i.e: "crop", "cover", "contain", "width", "height", "thumbnail" 41 | :return optimized image data. 42 | """ 43 | if OPTIMIZED_IMAGE_METHOD == "pillow": 44 | image = Image.open(image_data) 45 | bytes_io = BytesIO() 46 | 47 | extension = get_image_extension(image) 48 | 49 | # If output_size is set, resize the image with the selected 50 | # resize_method. 'thumbnail' is used by default 51 | if output_size is not None: 52 | if resize_method: 53 | image = resizeimage.resize( 54 | method=resize_method, 55 | image=image, 56 | size=output_size, 57 | ) 58 | 59 | output_image = Image.new( 60 | "RGBA", 61 | output_size, 62 | BACKGROUND_TRANSPARENT, 63 | ) 64 | output_image_center = ( 65 | int((output_size[0] - image.size[0]) / 2), 66 | int((output_size[1] - image.size[1]) / 2), 67 | ) 68 | output_image.paste(image, output_image_center) 69 | else: 70 | # If output_size is None the output_image 71 | # would be the same as source 72 | output_image = image 73 | 74 | # If the file extension is JPEG, convert the output_image to RGB 75 | if extension == "JPEG": 76 | output_image = output_image.convert("RGB") 77 | 78 | output_image.save(bytes_io, format=extension, optimize=True) 79 | 80 | image_data.seek(0) 81 | image_data.file.write(bytes_io.getvalue()) 82 | image_data.file.truncate() 83 | 84 | elif OPTIMIZED_IMAGE_METHOD == "tinypng": 85 | # disable warning info 86 | requests.packages.urllib3.disable_warnings() 87 | 88 | # just info for people 89 | if any([output_size, resize_method]): 90 | message = ( 91 | '[django-image-optimizer] "output_size" and "resize_method" ' 92 | 'only for OPTIMIZED_IMAGE_METHOD="pillow"' 93 | ) 94 | logging.info(message) 95 | 96 | tinify.key = TINYPNG_KEY 97 | optimized_buffer = tinify.from_buffer( 98 | image_data.file.read() 99 | ).to_buffer() # noqa: E501 100 | image_data.seek(0) 101 | image_data.file.write(optimized_buffer) 102 | image_data.file.truncate() 103 | 104 | return image_data 105 | 106 | 107 | def crop_image_on_axis(image, width, height, x, y, extension): 108 | """ 109 | function to crop the image using axis (using Pillow). 110 | :param `image` is image data, e.g from request.FILES['image'] 111 | :param `width` float width of image 112 | :param `height` float height of image 113 | :param `x` is float x axis 114 | :param `y` is float y axis 115 | :param `extension` is string, e.g: ".png" 116 | """ 117 | # Open the passed image 118 | img = Image.open(image) 119 | 120 | # Initialise bytes io 121 | bytes_io = BytesIO() 122 | 123 | # crop the image through axis 124 | img = img.crop((x, y, width + x, height + y)) 125 | 126 | # resize the image and optimise it for file size, 127 | # making smaller as possible 128 | img = img.resize((width, height), Image.ANTIALIAS) 129 | 130 | # This line is optional, for safe side, image name should be unique. 131 | img.name = "{}.{}".format(uuid4().hex, extension) 132 | 133 | # If the file extension is JPEG, convert the output_image to RGB 134 | if extension == "JPEG": 135 | img = image.convert("RGB") 136 | img.save(bytes_io, format=extension, optimize=True) 137 | 138 | # return the image 139 | image.seek(0) 140 | 141 | # Write back new image 142 | image.file.write(bytes_io.getvalue()) 143 | 144 | # truncate the file size 145 | image.file.truncate() 146 | return image 147 | -------------------------------------------------------------------------------- /image_optimizer_demo/app_demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/django-image-optimizer/323659dc81cbdf2c068789550e81fa4b97ac2bc9/image_optimizer_demo/app_demo/__init__.py -------------------------------------------------------------------------------- /image_optimizer_demo/app_demo/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from app_demo.models import ( 3 | Post, 4 | Collaborator, 5 | OtherImage, 6 | ) 7 | from app_demo.forms import CropImageAxisForm 8 | 9 | 10 | class PostAdmin(admin.ModelAdmin): 11 | list_display = ["title", "created"] 12 | list_filter = ["created"] 13 | 14 | 15 | class CollaboratorAdmin(admin.ModelAdmin): 16 | list_display = ["name", "created"] 17 | list_filter = ["created"] 18 | 19 | 20 | class CropImageAxisAdmin(admin.ModelAdmin): 21 | list_display = ["created", "image"] 22 | list_filter = ["created"] 23 | form = CropImageAxisForm 24 | 25 | def get_form(self, request, *args, **kwargs): 26 | form = super().get_form(request, *args, **kwargs) 27 | form.request = request 28 | return form 29 | 30 | 31 | admin.site.register(Post, PostAdmin) 32 | admin.site.register(Collaborator, CollaboratorAdmin) 33 | admin.site.register(OtherImage, CropImageAxisAdmin) 34 | -------------------------------------------------------------------------------- /image_optimizer_demo/app_demo/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AppDemoConfig(AppConfig): 5 | name = "app_demo" 6 | -------------------------------------------------------------------------------- /image_optimizer_demo/app_demo/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import OtherImage 3 | from image_optimizer.utils import crop_image_on_axis, get_file_extension 4 | 5 | 6 | class CropImageAxisForm(forms.ModelForm): 7 | width = forms.IntegerField() 8 | height = forms.IntegerField() 9 | x = forms.FloatField() 10 | y = forms.FloatField() 11 | 12 | def save(self, commit=True): 13 | instance = super().save(commit=False) 14 | request = self.request 15 | 16 | # process on create only 17 | image = request.FILES.get("image") 18 | if image is not None: 19 | width = float(request.POST["width"]) 20 | height = float(request.POST["height"]) 21 | x = float(request.POST["x"]) 22 | y = float(request.POST["y"]) 23 | extension = get_file_extension(image.name) 24 | 25 | try: 26 | image = crop_image_on_axis(image, width, height, x, y, extension) 27 | except ValueError as error: 28 | raise forms.ValidationError(error) 29 | 30 | instance.image = image 31 | instance.save() 32 | return instance 33 | 34 | return super().save(commit) 35 | 36 | class Meta: 37 | model = OtherImage 38 | fields = ["image"] 39 | -------------------------------------------------------------------------------- /image_optimizer_demo/app_demo/migrations/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | !__init__.py 3 | -------------------------------------------------------------------------------- /image_optimizer_demo/app_demo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/django-image-optimizer/323659dc81cbdf2c068789550e81fa4b97ac2bc9/image_optimizer_demo/app_demo/migrations/__init__.py -------------------------------------------------------------------------------- /image_optimizer_demo/app_demo/models.py: -------------------------------------------------------------------------------- 1 | """app_demo models.""" 2 | from django.db import models 3 | from image_optimizer.fields import OptimizedImageField 4 | 5 | 6 | class Post(models.Model): 7 | """ 8 | Post model. 9 | This model represents a Blog Post with a few fields including a `photo` 10 | field which is an OptimizedImageField instance without any optional 11 | argument. This means that out Post photo would keep source image original 12 | size. 13 | """ 14 | 15 | title = models.CharField(max_length=100) 16 | photo = OptimizedImageField(upload_to="uploads/posts/%Y/%m/%d") 17 | created = models.DateTimeField(auto_now_add=True) 18 | 19 | def __str__(self): 20 | return self.title 21 | 22 | class Meta: 23 | ordering = ["-created"] 24 | 25 | 26 | class Collaborator(models.Model): 27 | """ 28 | Collaborator model. 29 | This model represents a Blog Collaborator(a.k.a. Writter) with a few 30 | fields including a `profile_image` field which is an OptimizedImageField 31 | instance with `optimized_image_output_size` and 32 | `optimized_image_resize_method` arguments set. 33 | This means that our Collaborator profile_image would be a resized 34 | version of the source image, meant to keep a given screen resolution, 35 | in this case (400, 300) pixels. 36 | """ 37 | 38 | name = models.CharField(max_length=100) 39 | profile_image = OptimizedImageField( 40 | upload_to="uploads/collaborators/%Y/%m/%d", 41 | optimized_image_output_size=(400, 300), 42 | optimized_image_resize_method="cover", # 'thumbnail' or 'cover' 43 | ) 44 | created = models.DateTimeField(auto_now_add=True) 45 | 46 | def __str__(self): 47 | return self.name 48 | 49 | class Meta: 50 | ordering = ["-created"] 51 | 52 | 53 | class OtherImage(models.Model): 54 | image = models.ImageField(upload_to="uploads/%Y/%m/%d") 55 | created = models.DateTimeField(auto_now_add=True) 56 | 57 | def __str__(self): 58 | return str(self.image) 59 | 60 | class Meta: 61 | ordering = ["-created"] 62 | -------------------------------------------------------------------------------- /image_optimizer_demo/image_optimizer_demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/django-image-optimizer/323659dc81cbdf2c068789550e81fa4b97ac2bc9/image_optimizer_demo/image_optimizer_demo/__init__.py -------------------------------------------------------------------------------- /image_optimizer_demo/image_optimizer_demo/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for image_optimizer_demo project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "*^mmp(4)=8vx5%aksf+$#446gs974xyp4--2&+zp(ezsq0oxxs" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ["*"] 29 | 30 | OPTIMIZED_IMAGE_METHOD = "pillow" # 'tinypng' 31 | TINYPNG_KEY = "key-key-key-key" 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | "django.contrib.admin", 38 | "django.contrib.auth", 39 | "django.contrib.contenttypes", 40 | "django.contrib.sessions", 41 | "django.contrib.messages", 42 | "django.contrib.staticfiles", 43 | "image_optimizer", 44 | "app_demo", 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | "django.middleware.security.SecurityMiddleware", 49 | "django.contrib.sessions.middleware.SessionMiddleware", 50 | "django.middleware.common.CommonMiddleware", 51 | "django.middleware.csrf.CsrfViewMiddleware", 52 | "django.contrib.auth.middleware.AuthenticationMiddleware", 53 | "django.contrib.messages.middleware.MessageMiddleware", 54 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 55 | ] 56 | 57 | ROOT_URLCONF = "image_optimizer_demo.urls" 58 | 59 | TEMPLATES = [ 60 | { 61 | "BACKEND": "django.template.backends.django.DjangoTemplates", 62 | "DIRS": [], 63 | "APP_DIRS": True, 64 | "OPTIONS": { 65 | "context_processors": [ 66 | "django.template.context_processors.debug", 67 | "django.template.context_processors.request", 68 | "django.contrib.auth.context_processors.auth", 69 | "django.contrib.messages.context_processors.messages", 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = "image_optimizer_demo.wsgi.application" 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 80 | 81 | DATABASES = { 82 | "default": { 83 | "ENGINE": "django.db.backends.sqlite3", 84 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 85 | } 86 | } 87 | 88 | 89 | # Password validation 90 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 91 | 92 | AUTH_PASSWORD_VALIDATORS = [ 93 | { 94 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 95 | }, 96 | { 97 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 98 | }, 99 | { 100 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 101 | }, 102 | { 103 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 104 | }, 105 | ] 106 | 107 | 108 | # Internationalization 109 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 110 | 111 | LANGUAGE_CODE = "en-us" 112 | 113 | TIME_ZONE = "UTC" 114 | 115 | USE_I18N = True 116 | 117 | USE_L10N = True 118 | 119 | USE_TZ = True 120 | 121 | 122 | # Static files (CSS, JavaScript, Images) 123 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 124 | 125 | 126 | STATIC_URL = "/static/" 127 | MEDIA_URL = "/media/" 128 | STATIC_ROOT = os.path.join(BASE_DIR, "static") 129 | MEDIA_ROOT = os.path.join(BASE_DIR, "media") 130 | -------------------------------------------------------------------------------- /image_optimizer_demo/image_optimizer_demo/urls.py: -------------------------------------------------------------------------------- 1 | """image_optimizer_demo URL Configuration 2 | The `urlpatterns` list routes URLs to views. For more information please see: 3 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 4 | Examples: 5 | Function views 6 | 1. Add an import: from my_app import views 7 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 8 | Class-based views 9 | 1. Add an import: from other_app.views import Home 10 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 11 | Including another URLconf 12 | 1. Import the include() function: from django.urls import include, path 13 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 14 | """ 15 | from django.contrib import admin 16 | from django.urls import path 17 | from django.conf import settings 18 | from django.conf.urls.static import static 19 | 20 | urlpatterns = ( 21 | [ 22 | path("admin/", admin.site.urls), 23 | ] 24 | + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 25 | + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 26 | ) 27 | -------------------------------------------------------------------------------- /image_optimizer_demo/image_optimizer_demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for image_optimizer_demo project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "image_optimizer_demo.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /image_optimizer_demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "image_optimizer_demo.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo ''; 4 | echo -n "[+] commit ➜ "; 5 | read commit; 6 | 7 | if [ "$commit" ]; then 8 | git add .; 9 | git commit -m "$commit"; 10 | git push -u origin master; 11 | fi 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E402,E501,W503,W504,E731,E741 3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 4 | 5 | [pycodestyle] 6 | max-line-length = 120 7 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | __VERSION__ = "1.0.3" 6 | __AUTHOR__ = "Agus Makmun (Summon Agus)" 7 | __AUTHOR_EMAIL__ = "summon.agus@gmail.com" 8 | 9 | install_requires = [ 10 | "Django", 11 | "Pillow", 12 | "requests", 13 | "tinify", 14 | "python-resize-image", 15 | ] 16 | 17 | setup( 18 | name="django-image-optimizer", 19 | version=__VERSION__, 20 | author=__AUTHOR__, 21 | author_email=__AUTHOR_EMAIL__, 22 | description="Django Image Optimizer (Compressor)", 23 | packages=find_packages(exclude=["*demo"]), 24 | include_package_data=True, 25 | zip_safe=False, 26 | url="https://github.com/agusmakmun/django-image-optimizer", 27 | download_url=( 28 | "https://github.com/agusmakmun/django-image-optimizer/tarball/v%s" % __VERSION__ 29 | ), 30 | keywords=["image optimizer", "django image optimizer", "image optimizer"], 31 | long_description=open("README.rst").read(), 32 | license="MIT", 33 | classifiers=[ 34 | "Development Status :: 5 - Production/Stable", 35 | "Environment :: Web Environment", 36 | "Framework :: Django", 37 | "Framework :: Django :: 1.8", 38 | "Framework :: Django :: 1.9", 39 | "Framework :: Django :: 1.10", 40 | "Framework :: Django :: 1.11", 41 | "Framework :: Django :: 2.0", 42 | "Intended Audience :: Developers", 43 | "Operating System :: OS Independent", 44 | "Programming Language :: Python", 45 | "Programming Language :: Python :: 2.7", 46 | "Programming Language :: Python :: 3.3", 47 | "Programming Language :: Python :: 3.5", 48 | "Programming Language :: Python :: 3.7", 49 | "Topic :: Software Development :: Libraries :: Python Modules", 50 | "License :: OSI Approved :: MIT License", 51 | ], 52 | install_requires=install_requires, 53 | ) 54 | --------------------------------------------------------------------------------