mirror of
https://github.com/jazzband/django-avatar.git
synced 2026-03-17 06:30:31 +00:00
Compare commits
No commits in common. "main" and "v8.0.1" have entirely different histories.
10 changed files with 67 additions and 83 deletions
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
|
@ -11,12 +11,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
|
|
|
|||
36
.github/workflows/test.yml
vendored
36
.github/workflows/test.yml
vendored
|
|
@ -5,26 +5,32 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
|
||||
django-version: ['4.2', '5.2', '6.0.*']
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
|
||||
django-version: ['3.2', '4.1', '4.2', '5.0', '5.1.*']
|
||||
exclude:
|
||||
- python-version: 3.13
|
||||
django-version: 4.2
|
||||
|
||||
- python-version: 3.14
|
||||
django-version: 4.2
|
||||
|
||||
- python-version: 3.10
|
||||
django-version: 6.0.*
|
||||
|
||||
- python-version: 3.11
|
||||
django-version: 6.0.*
|
||||
django-version: 3.2
|
||||
|
||||
- python-version: 3.12
|
||||
django-version: 3.2
|
||||
|
||||
- python-version: 3.8
|
||||
django-version: 5.0
|
||||
|
||||
- python-version: 3.9
|
||||
django-version: 5.0
|
||||
|
||||
- python-version: 3.8
|
||||
django-version: 5.1.*
|
||||
|
||||
- python-version: 3.9
|
||||
django-version: 5.1.*
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: 'Set up Python ${{ matrix.python-version }}'
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '${{ matrix.python-version }}'
|
||||
cache: 'pip'
|
||||
|
|
@ -42,4 +48,4 @@ jobs:
|
|||
coverage report
|
||||
coverage xml
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v3
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: "7.0.0"
|
||||
rev: "5.13.2"
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 25.12.0
|
||||
rev: 24.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--target-version=py310]
|
||||
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: '7.3.0'
|
||||
rev: '7.1.1'
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
Changelog
|
||||
=========
|
||||
* 9.0.0
|
||||
* Fix files not closed in `create_thumbnail`
|
||||
* Add Django 5.2 and 6.0 support
|
||||
* Add Python 3.13, 3.14 support
|
||||
* Drop Python 3.8, 3.9 support
|
||||
|
||||
* 8.0.1
|
||||
* Fix Django 5.1 compatibility
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "9.0.0"
|
||||
__version__ = "8.0.1"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
from contextlib import closing
|
||||
from io import BytesIO
|
||||
|
||||
from django.core.files import File
|
||||
|
|
@ -143,38 +142,38 @@ class Avatar(models.Model):
|
|||
orig = self.avatar.storage.open(self.avatar.name, "rb")
|
||||
except IOError:
|
||||
return # What should we do here? Render a "sorry, didn't work" img?
|
||||
|
||||
with closing(orig):
|
||||
try:
|
||||
image = Image.open(orig)
|
||||
except IOError:
|
||||
thumb_file = File(orig)
|
||||
try:
|
||||
image = Image.open(orig)
|
||||
image = self.transpose_image(image)
|
||||
quality = quality or settings.AVATAR_THUMB_QUALITY
|
||||
w, h = image.size
|
||||
if w != width or h != height:
|
||||
ratioReal = 1.0 * w / h
|
||||
ratioWant = 1.0 * width / height
|
||||
if ratioReal > ratioWant:
|
||||
diff = int((w - (h * ratioWant)) / 2)
|
||||
image = image.crop((diff, 0, w - diff, h))
|
||||
elif ratioReal < ratioWant:
|
||||
diff = int((h - (w / ratioWant)) / 2)
|
||||
image = image.crop((0, diff, w, h - diff))
|
||||
if settings.AVATAR_THUMB_FORMAT == "JPEG" and image.mode == "RGBA":
|
||||
image = image.convert("RGB")
|
||||
elif image.mode not in (settings.AVATAR_THUMB_MODES):
|
||||
image = image.convert(settings.AVATAR_THUMB_MODES[0])
|
||||
image = image.resize((width, height), settings.AVATAR_RESIZE_METHOD)
|
||||
thumb = BytesIO()
|
||||
image.save(thumb, settings.AVATAR_THUMB_FORMAT, quality=quality)
|
||||
thumb_file = ContentFile(thumb.getvalue())
|
||||
else:
|
||||
image = self.transpose_image(image)
|
||||
quality = quality or settings.AVATAR_THUMB_QUALITY
|
||||
w, h = image.size
|
||||
if w != width or h != height:
|
||||
ratioReal = 1.0 * w / h
|
||||
ratioWant = 1.0 * width / height
|
||||
if ratioReal > ratioWant:
|
||||
diff = int((w - (h * ratioWant)) / 2)
|
||||
image = image.crop((diff, 0, w - diff, h))
|
||||
elif ratioReal < ratioWant:
|
||||
diff = int((h - (w / ratioWant)) / 2)
|
||||
image = image.crop((0, diff, w, h - diff))
|
||||
if settings.AVATAR_THUMB_FORMAT == "JPEG" and image.mode == "RGBA":
|
||||
image = image.convert("RGB")
|
||||
elif image.mode not in (settings.AVATAR_THUMB_MODES):
|
||||
image = image.convert(settings.AVATAR_THUMB_MODES[0])
|
||||
image = image.resize((width, height), settings.AVATAR_RESIZE_METHOD)
|
||||
thumb = BytesIO()
|
||||
image.save(thumb, settings.AVATAR_THUMB_FORMAT, quality=quality)
|
||||
thumb_file = ContentFile(thumb.getvalue())
|
||||
else:
|
||||
thumb_file = File(orig)
|
||||
thumb_file = File(orig)
|
||||
thumb_name = self.avatar_name(width, height)
|
||||
thumb = self.avatar.storage.save(thumb_name, thumb_file)
|
||||
invalidate_cache(self.user, width, height)
|
||||
except IOError:
|
||||
thumb_file = File(orig)
|
||||
thumb = self.avatar.storage.save(
|
||||
self.avatar_name(width, height), thumb_file
|
||||
)
|
||||
invalidate_cache(self.user, width, height)
|
||||
|
||||
def avatar_url(self, width, height=None):
|
||||
return self.avatar.storage.url(self.avatar_name(width, height))
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class GravatarAvatarProvider(object):
|
|||
|
||||
class LibRAvatarProvider:
|
||||
"""
|
||||
Returns the url of an avatar by the LibRavatar service.
|
||||
Returns the url of an avatar by the Ravatar service.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
|
|
@ -87,17 +87,8 @@ class LibRAvatarProvider:
|
|||
baseurl = "http://" + hostname + ":" + port + "/avatar/"
|
||||
except Exception:
|
||||
baseurl = "https://seccdn.libravatar.org/avatar/"
|
||||
|
||||
params = {"s": str(width)}
|
||||
if settings.AVATAR_GRAVATAR_DEFAULT:
|
||||
params["d"] = settings.AVATAR_GRAVATAR_DEFAULT
|
||||
if settings.AVATAR_GRAVATAR_FORCEDEFAULT:
|
||||
params["f"] = "y"
|
||||
path = "%s/?%s" % (
|
||||
hashlib.md5(force_bytes(email.strip().lower())).hexdigest(),
|
||||
urlencode(params),
|
||||
)
|
||||
return urljoin(baseurl, path)
|
||||
hash = hashlib.md5(email.strip().lower()).hexdigest()
|
||||
return baseurl + hash
|
||||
|
||||
|
||||
class FacebookAvatarProvider(object):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{% block content %}
|
||||
{% if not avatars %}
|
||||
{% url 'avatar:change' as avatar_change_url %}
|
||||
{% url 'avatar_change' as avatar_change_url %}
|
||||
<p>{% blocktrans %}You have no avatars to delete. Please <a href="{{ avatar_change_url }}">upload one</a> now.{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<p>{% trans "Please select the avatars that you would like to delete." %}</p>
|
||||
|
|
|
|||
|
|
@ -13,20 +13,20 @@ keywords=["avatar", "django"]
|
|||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Django",
|
||||
"Intended Audience :: Developers",
|
||||
"Framework :: Django",
|
||||
"Framework :: Django :: 3.2",
|
||||
"Framework :: Django :: 4.1",
|
||||
"Framework :: Django :: 4.2",
|
||||
"Framework :: Django :: 5.0",
|
||||
"Framework :: Django :: 5.2",
|
||||
"Framework :: Django :: 6.0",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
]
|
||||
dynamic = ["version", "dependencies"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import math
|
||||
import os.path
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from unittest import skipIf
|
||||
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.core import management
|
||||
|
|
@ -120,7 +118,6 @@ class AvatarTests(TestCase):
|
|||
self.assertTrue(avatar.primary)
|
||||
|
||||
# We allow the .tiff file extension but not the mime type
|
||||
@skipIf(sys.platform == "win32", "Skipping test on Windows platform")
|
||||
@override_settings(AVATAR_ALLOWED_FILE_EXTS=(".png", ".gif", ".jpg", ".tiff"))
|
||||
@override_settings(
|
||||
AVATAR_ALLOWED_MIMETYPES=("image/png", "image/gif", "image/jpeg")
|
||||
|
|
@ -133,7 +130,6 @@ class AvatarTests(TestCase):
|
|||
self.assertNotEqual(response.context["upload_avatar_form"].errors, {})
|
||||
|
||||
# We allow the .tiff file extension and the mime type
|
||||
@skipIf(sys.platform == "win32", "Skipping test on Windows platform")
|
||||
@override_settings(AVATAR_ALLOWED_FILE_EXTS=(".png", ".gif", ".jpg", ".tiff"))
|
||||
@override_settings(
|
||||
AVATAR_ALLOWED_MIMETYPES=("image/png", "image/gif", "image/jpeg", "image/tiff")
|
||||
|
|
@ -145,7 +141,6 @@ class AvatarTests(TestCase):
|
|||
self.assertEqual(len(response.redirect_chain), 1) # Redirect only if it worked
|
||||
self.assertEqual(response.context["upload_avatar_form"].errors, {})
|
||||
|
||||
@skipIf(sys.platform == "win32", "Skipping test on Windows platform")
|
||||
@override_settings(AVATAR_ALLOWED_FILE_EXTS=(".jpg", ".png"))
|
||||
def test_image_without_wrong_extension(self):
|
||||
response = upload_helper(self, "imagefilewithoutext")
|
||||
|
|
@ -153,7 +148,6 @@ class AvatarTests(TestCase):
|
|||
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
|
||||
self.assertNotEqual(response.context["upload_avatar_form"].errors, {})
|
||||
|
||||
@skipIf(sys.platform == "win32", "Skipping test on Windows platform")
|
||||
@override_settings(AVATAR_ALLOWED_FILE_EXTS=(".jpg", ".png"))
|
||||
def test_image_with_wrong_extension(self):
|
||||
response = upload_helper(self, "imagefilewithwrongext.ogg")
|
||||
|
|
|
|||
Loading…
Reference in a new issue