-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 16622d7
Showing
12 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
MANIFEST | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*.cover | ||
.hypothesis/ | ||
.pytest_cache/ | ||
|
||
# Translations | ||
*.mo | ||
*.pot | ||
|
||
# Django stuff: | ||
*.log | ||
local_settings.py | ||
db.sqlite3 | ||
|
||
# Flask stuff: | ||
instance/ | ||
.webassets-cache | ||
|
||
# Scrapy stuff: | ||
.scrapy | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# pyenv | ||
.python-version | ||
|
||
# celery beat schedule file | ||
celerybeat-schedule | ||
|
||
# SageMath parsed files | ||
*.sage.py | ||
|
||
# Environments | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
.spyproject | ||
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# mkdocs documentation | ||
/site | ||
|
||
# mypy | ||
.mypy_cache/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2018 Lucas Campos Teixeira e Nascimento | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
include LICENSE | ||
include README.rst | ||
recursive-include docs * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
===== | ||
Django-remote-image | ||
===== | ||
|
||
Django-remote-image is a Django app that adds a new form field for images. | ||
The default widget is a text input, that accepts a URL of a image. | ||
|
||
The image is downloaded and can be passed to a ``ImageField`` in a model. Pillow needs to be installed. | ||
|
||
It is possible to whitelist and blacklist file extensions. | ||
|
||
Examples are shown below. | ||
|
||
Quick start | ||
----------- | ||
|
||
$ pip install django-remote-image | ||
|
||
Using | ||
----------- | ||
Using the field in a form: | ||
|
||
.. code:: python | ||
import remote_image import RemoteImageField | ||
class ExampleForm(forms.Form): | ||
image = RemoteImageField() | ||
Whitelisting file extensions (only the ones in the list will be permitted): | ||
|
||
.. code:: python | ||
import remote_image import RemoteImageField | ||
class ExampleForm(forms.Form): | ||
image = RemoteImageField(ext_whitelist=['png', 'jpg']) | ||
Blacklisting file extensions (the ones in the list will be blocked): | ||
|
||
.. code:: python | ||
import remote_image import RemoteImageField | ||
class ExampleForm(forms.Form): | ||
image = RemoteImageField(ext_blacklist=['png', 'jpg']) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from django.contrib import admin | ||
|
||
# Register your models here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class ImageDownloadConfig(AppConfig): | ||
name = 'image_download' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from django import forms | ||
from django.forms import ModelForm | ||
|
||
from .fields import RemoteImageField | ||
|
||
class ExampleForm(forms.Form): | ||
remote_image = RemoteImageField(required=True) | ||
|
||
class ExampleWhitelistedPNGForm(forms.Form): | ||
remote_image = RemoteImageField(required=True, ext_whitelist=['png']) | ||
|
||
class ExampleBlacklistedPNGForm(forms.Form): | ||
remote_image = RemoteImageField(required=True, ext_blacklist=['png']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import copy | ||
import io | ||
import urllib.request | ||
|
||
from django.core import validators | ||
from django.core.exceptions import ValidationError | ||
from django.core.files.base import ContentFile | ||
from django.core.files.uploadedfile import InMemoryUploadedFile | ||
from django.forms.fields import ImageField | ||
from django.forms.widgets import URLInput | ||
from django.utils.translation import gettext_lazy as _ | ||
from PIL import Image as ImagePIL | ||
|
||
|
||
class RemoteImageField(ImageField): | ||
widget = URLInput | ||
default_error_messages = { | ||
'not_whitelisted': _( | ||
"The format of this file is not whitelisted." | ||
), | ||
'is_blacklisted': _( | ||
"The format of this file is blacklisted." | ||
), | ||
} | ||
|
||
def __init__(self, *, ext_whitelist=None, ext_blacklist=None, **kwargs): | ||
self.ext_whitelist = ext_whitelist | ||
self.ext_blacklist = ext_blacklist | ||
super().__init__(**kwargs) | ||
|
||
def to_python(self, url): | ||
data = self.get_remote_image_as_InMemoryUploadedFile(url) | ||
return super().to_python(data) | ||
|
||
def get_remote_image_as_InMemoryUploadedFile(self, url): | ||
image_bytesio = self.download_and_return_BytesIO(url) | ||
return self.BytesIO_to_InMemoryUploadedFile(image_bytesio) | ||
|
||
def download_and_return_BytesIO(self, url): | ||
response = urllib.request.urlopen(url) | ||
img_bytes = response.read() | ||
img_bytesio = io.BytesIO(img_bytes) | ||
return img_bytesio | ||
|
||
def BytesIO_to_InMemoryUploadedFile(self, img_bytesio): | ||
img_length = img_bytesio.getbuffer().nbytes | ||
img_format = self.BytesIO_to_PIL(img_bytesio).format.lower() | ||
img_name = "tempfile." + img_format | ||
img_content_type = "image/" + img_format | ||
|
||
if self.ext_whitelist != None and img_format not in self.ext_whitelist: | ||
raise ValidationError( | ||
self.error_messages['not_whitelisted'], | ||
code='not_whitelisted', | ||
) | ||
|
||
if self.ext_blacklist != None and img_format in self.ext_blacklist: | ||
raise ValidationError( | ||
self.error_messages['is_blacklisted'], | ||
code='is_blacklisted', | ||
) | ||
|
||
return InMemoryUploadedFile( | ||
img_bytesio, | ||
field_name='tempfile', | ||
name=img_name, | ||
content_type=img_content_type, | ||
size=img_length, | ||
charset='utf-8', | ||
) | ||
|
||
def BytesIO_to_PIL(self, img_bytesio): | ||
img_copy = copy.copy(img_bytesio) | ||
return ImagePIL.open(img_copy) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
from io import BytesIO | ||
|
||
from django.core.files.uploadedfile import InMemoryUploadedFile | ||
from django.forms.widgets import URLInput | ||
from django.test import TestCase | ||
|
||
from .example_forms import (ExampleBlacklistedPNGForm, ExampleForm, | ||
ExampleWhitelistedPNGForm) | ||
from .fields import RemoteImageField | ||
|
||
|
||
class RemoteImageTestCase(TestCase): | ||
def setUp(self): | ||
pass | ||
|
||
def test_bytes_length_correct_on_img_download(self): | ||
image_url = "https://upload.wikimedia.org/wikipedia/commons/d/d9/Test.png" | ||
img_bytesio = RemoteImageField().download_and_return_BytesIO(image_url) | ||
img_length = img_bytesio.getbuffer().nbytes | ||
|
||
self.assertEqual(3118, img_length) | ||
|
||
def test_remote_image_field_with_png(self): | ||
image_url = "https://upload.wikimedia.org/wikipedia/commons/d/d9/Test.png" | ||
|
||
data = {"remote_image": image_url} | ||
form = ExampleForm(data) | ||
self.assertTrue(form.is_valid()) | ||
|
||
def test_remote_image_field_with_jpg(self): | ||
image_url = "https://upload.wikimedia.org/wikipedia/commons/5/5b/Name.jpg" | ||
|
||
data = {"remote_image": image_url} | ||
form = ExampleForm(data) | ||
self.assertTrue(form.is_valid()) | ||
|
||
def test_remote_image_field_with_gif(self): | ||
image_url = "https://upload.wikimedia.org/wikipedia/commons/b/bd/Name.gif" | ||
|
||
data = {"remote_image": image_url} | ||
form = ExampleForm(data) | ||
self.assertTrue(form.is_valid()) | ||
|
||
def test_remote_image_field_with_whitelist_working(self): | ||
image_url = "https://upload.wikimedia.org/wikipedia/commons/d/d9/Test.png" | ||
|
||
data = {"remote_image": image_url} | ||
form = ExampleWhitelistedPNGForm(data) | ||
self.assertTrue(form.is_valid()) | ||
|
||
def test_remote_image_field_with_blacklist_working(self): | ||
image_url = "https://upload.wikimedia.org/wikipedia/commons/d/d9/Test.png" | ||
|
||
data = {"remote_image": image_url} | ||
form = ExampleBlacklistedPNGForm(data) | ||
self.assertFalse(form.is_valid()) | ||
|
||
def test_remote_image_field_with_extension_not_on_whitelist(self): | ||
image_url = "https://upload.wikimedia.org/wikipedia/commons/b/bd/Name.gif" | ||
|
||
data = {"remote_image": image_url} | ||
form = ExampleWhitelistedPNGForm(data) | ||
self.assertFalse(form.is_valid()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import os | ||
from setuptools import find_packages, setup | ||
|
||
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: | ||
README = readme.read() | ||
|
||
# allow setup.py to be run from any path | ||
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) | ||
|
||
setup( | ||
name='django-remote-image', | ||
version='0.1', | ||
packages=find_packages(), | ||
include_package_data=True, | ||
license='MIT License', # example license | ||
description='Custom django form field for downloading images from a URL on a URLInput.', | ||
long_description=README, | ||
url='https://github.com/LucasCTN/django-remote-image', | ||
author='Lucas Nascimento', | ||
author_email='[email protected]', | ||
classifiers=[ | ||
'Environment :: Web Environment', | ||
'Framework :: Django', | ||
'Intended Audience :: Developers', | ||
'License :: OSI Approved :: MIT License', | ||
'Operating System :: OS Independent', | ||
'Programming Language :: Python', | ||
'Programming Language :: Python :: 3.5', | ||
'Programming Language :: Python :: 3.6', | ||
'Topic :: Internet :: WWW/HTTP', | ||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content', | ||
], | ||
) |