diff --git a/avatar/__init__.py b/avatar/__init__.py
index a0f6658..ba7be38 100644
--- a/avatar/__init__.py
+++ b/avatar/__init__.py
@@ -1 +1 @@
-__version__ = '5.0.0'
+__version__ = "5.0.0"
diff --git a/avatar/admin.py b/avatar/admin.py
index c023c49..29ff967 100644
--- a/avatar/admin.py
+++ b/avatar/admin.py
@@ -10,21 +10,25 @@ from avatar.utils import get_user_model
class AvatarAdmin(admin.ModelAdmin):
- list_display = ('get_avatar', 'user', 'primary', "date_uploaded")
- list_filter = ('primary',)
- search_fields = ('user__%s' % getattr(get_user_model(), 'USERNAME_FIELD', 'username'),)
+ list_display = ("get_avatar", "user", "primary", "date_uploaded")
+ list_filter = ("primary",)
+ search_fields = (
+ "user__%s" % getattr(get_user_model(), "USERNAME_FIELD", "username"),
+ )
list_per_page = 50
def get_avatar(self, avatar_in):
- context = dict({
- 'user': avatar_in.user,
- 'url': avatar_in.avatar.url,
- 'alt': six.text_type(avatar_in.user),
- 'size': 80,
- })
- return render_to_string('avatar/avatar_tag.html', context)
+ context = dict(
+ {
+ "user": avatar_in.user,
+ "url": avatar_in.avatar.url,
+ "alt": six.text_type(avatar_in.user),
+ "size": 80,
+ }
+ )
+ return render_to_string("avatar/avatar_tag.html", context)
- get_avatar.short_description = _('Avatar')
+ get_avatar.short_description = _("Avatar")
get_avatar.allow_tags = True
def save_model(self, request, obj, form, change):
diff --git a/avatar/apps.py b/avatar/apps.py
index dcff8cd..0b91ed4 100644
--- a/avatar/apps.py
+++ b/avatar/apps.py
@@ -2,5 +2,5 @@ from django.apps import AppConfig
class Config(AppConfig):
- name = 'avatar'
- default_auto_field = 'django.db.models.AutoField'
+ name = "avatar"
+ default_auto_field = "django.db.models.AutoField"
diff --git a/avatar/conf.py b/avatar/conf.py
index 38dd8e2..937f874 100644
--- a/avatar/conf.py
+++ b/avatar/conf.py
@@ -8,16 +8,16 @@ from appconf import AppConf
class AvatarConf(AppConf):
DEFAULT_SIZE = 80
RESIZE_METHOD = Image.ANTIALIAS
- STORAGE_DIR = 'avatars'
- PATH_HANDLER = 'avatar.models.avatar_path_handler'
- GRAVATAR_BASE_URL = 'https://www.gravatar.com/avatar/'
- GRAVATAR_FIELD = 'email'
+ STORAGE_DIR = "avatars"
+ PATH_HANDLER = "avatar.models.avatar_path_handler"
+ GRAVATAR_BASE_URL = "https://www.gravatar.com/avatar/"
+ GRAVATAR_FIELD = "email"
GRAVATAR_DEFAULT = None
AVATAR_GRAVATAR_FORCEDEFAULT = False
- DEFAULT_URL = 'avatar/img/default.jpg'
+ DEFAULT_URL = "avatar/img/default.jpg"
MAX_AVATARS_PER_USER = 42
MAX_SIZE = 1024 * 1024
- THUMB_FORMAT = 'JPEG'
+ THUMB_FORMAT = "JPEG"
THUMB_QUALITY = 85
HASH_FILENAMES = False
HASH_USERDIRNAMES = False
@@ -30,15 +30,16 @@ class AvatarConf(AppConf):
FACEBOOK_GET_ID = None
CACHE_ENABLED = True
RANDOMIZE_HASHES = False
- ADD_TEMPLATE = ''
- CHANGE_TEMPLATE = ''
- DELETE_TEMPLATE = ''
+ ADD_TEMPLATE = ""
+ CHANGE_TEMPLATE = ""
+ DELETE_TEMPLATE = ""
PROVIDERS = (
- 'avatar.providers.PrimaryAvatarProvider',
- 'avatar.providers.GravatarAvatarProvider',
- 'avatar.providers.DefaultAvatarProvider',
+ "avatar.providers.PrimaryAvatarProvider",
+ "avatar.providers.GravatarAvatarProvider",
+ "avatar.providers.DefaultAvatarProvider",
)
def configure_auto_generate_avatar_sizes(self, value):
- return value or getattr(settings, 'AVATAR_AUTO_GENERATE_SIZES',
- (self.DEFAULT_SIZE,))
+ return value or getattr(
+ settings, "AVATAR_AUTO_GENERATE_SIZES", (self.DEFAULT_SIZE,)
+ )
diff --git a/avatar/forms.py b/avatar/forms.py
index 850b09b..9dbe800 100644
--- a/avatar/forms.py
+++ b/avatar/forms.py
@@ -14,70 +14,82 @@ from avatar.models import Avatar
def avatar_img(avatar, size):
if not avatar.thumbnail_exists(size):
avatar.create_thumbnail(size)
- return mark_safe('
' %
- (avatar.avatar_url(size), six.text_type(avatar),
- size, size))
+ return mark_safe(
+ '
'
+ % (avatar.avatar_url(size), six.text_type(avatar), size, size)
+ )
class UploadAvatarForm(forms.Form):
avatar = forms.ImageField(label=_("avatar"))
def __init__(self, *args, **kwargs):
- self.user = kwargs.pop('user')
+ self.user = kwargs.pop("user")
super(UploadAvatarForm, self).__init__(*args, **kwargs)
def clean_avatar(self):
- data = self.cleaned_data['avatar']
+ data = self.cleaned_data["avatar"]
if settings.AVATAR_ALLOWED_FILE_EXTS:
root, ext = os.path.splitext(data.name.lower())
if ext not in settings.AVATAR_ALLOWED_FILE_EXTS:
valid_exts = ", ".join(settings.AVATAR_ALLOWED_FILE_EXTS)
- error = _("%(ext)s is an invalid file extension. "
- "Authorized extensions are : %(valid_exts_list)s")
- raise forms.ValidationError(error
- % {'ext': ext,
- 'valid_exts_list': valid_exts})
+ error = _(
+ "%(ext)s is an invalid file extension. "
+ "Authorized extensions are : %(valid_exts_list)s"
+ )
+ raise forms.ValidationError(
+ error % {"ext": ext, "valid_exts_list": valid_exts}
+ )
if data.size > settings.AVATAR_MAX_SIZE:
- error = _("Your file is too big (%(size)s), "
- "the maximum allowed size is %(max_valid_size)s")
- raise forms.ValidationError(error
- % {'size': filesizeformat(data.size),
- 'max_valid_size': filesizeformat(settings.AVATAR_MAX_SIZE)})
+ error = _(
+ "Your file is too big (%(size)s), "
+ "the maximum allowed size is %(max_valid_size)s"
+ )
+ raise forms.ValidationError(
+ error
+ % {
+ "size": filesizeformat(data.size),
+ "max_valid_size": filesizeformat(settings.AVATAR_MAX_SIZE),
+ }
+ )
count = Avatar.objects.filter(user=self.user).count()
if 1 < settings.AVATAR_MAX_AVATARS_PER_USER <= count:
- error = _("You already have %(nb_avatars)d avatars, "
- "and the maximum allowed is %(nb_max_avatars)d.")
- raise forms.ValidationError(error % {
- 'nb_avatars': count,
- 'nb_max_avatars': settings.AVATAR_MAX_AVATARS_PER_USER,
- })
+ error = _(
+ "You already have %(nb_avatars)d avatars, "
+ "and the maximum allowed is %(nb_max_avatars)d."
+ )
+ raise forms.ValidationError(
+ error
+ % {
+ "nb_avatars": count,
+ "nb_max_avatars": settings.AVATAR_MAX_AVATARS_PER_USER,
+ }
+ )
return
class PrimaryAvatarForm(forms.Form):
-
def __init__(self, *args, **kwargs):
- kwargs.pop('user')
- size = kwargs.pop('size', settings.AVATAR_DEFAULT_SIZE)
- avatars = kwargs.pop('avatars')
+ kwargs.pop("user")
+ size = kwargs.pop("size", settings.AVATAR_DEFAULT_SIZE)
+ avatars = kwargs.pop("avatars")
super(PrimaryAvatarForm, self).__init__(*args, **kwargs)
choices = [(avatar.id, avatar_img(avatar, size)) for avatar in avatars]
- self.fields['choice'] = forms.ChoiceField(label=_("Choices"),
- choices=choices,
- widget=widgets.RadioSelect)
+ self.fields["choice"] = forms.ChoiceField(
+ label=_("Choices"), choices=choices, widget=widgets.RadioSelect
+ )
class DeleteAvatarForm(forms.Form):
-
def __init__(self, *args, **kwargs):
- kwargs.pop('user')
- size = kwargs.pop('size', settings.AVATAR_DEFAULT_SIZE)
- avatars = kwargs.pop('avatars')
+ kwargs.pop("user")
+ size = kwargs.pop("size", settings.AVATAR_DEFAULT_SIZE)
+ avatars = kwargs.pop("avatars")
super(DeleteAvatarForm, self).__init__(*args, **kwargs)
choices = [(avatar.id, avatar_img(avatar, size)) for avatar in avatars]
- self.fields['choices'] = forms.MultipleChoiceField(label=_("Choices"),
- choices=choices,
- widget=widgets.CheckboxSelectMultiple)
+ self.fields["choices"] = forms.MultipleChoiceField(
+ label=_("Choices"), choices=choices, widget=widgets.CheckboxSelectMultiple
+ )
diff --git a/avatar/management/commands/rebuild_avatars.py b/avatar/management/commands/rebuild_avatars.py
index 2061946..a6033f1 100644
--- a/avatar/management/commands/rebuild_avatars.py
+++ b/avatar/management/commands/rebuild_avatars.py
@@ -5,13 +5,15 @@ from avatar.models import Avatar
class Command(BaseCommand):
- help = ("Regenerates avatar thumbnails for the sizes specified in "
- "settings.AVATAR_AUTO_GENERATE_SIZES.")
+ help = (
+ "Regenerates avatar thumbnails for the sizes specified in "
+ "settings.AVATAR_AUTO_GENERATE_SIZES."
+ )
def handle(self, *args, **options):
for avatar in Avatar.objects.all():
for size in settings.AVATAR_AUTO_GENERATE_SIZES:
- if options['verbosity'] != 0:
+ if options["verbosity"] != 0:
print("Rebuilding Avatar id=%s at size %s." % (avatar.id, size))
avatar.create_thumbnail(size)
diff --git a/avatar/migrations/0001_initial.py b/avatar/migrations/0001_initial.py
index 06c16cb..7f2349d 100644
--- a/avatar/migrations/0001_initial.py
+++ b/avatar/migrations/0001_initial.py
@@ -13,13 +13,37 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
- name='Avatar',
+ name="Avatar",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('primary', models.BooleanField(default=False)),
- ('avatar', models.ImageField(storage=django.core.files.storage.FileSystemStorage(), max_length=1024, upload_to=avatar.models.avatar_file_path, blank=True)),
- ('date_uploaded', models.DateTimeField(default=django.utils.timezone.now)),
- ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
+ (
+ "id",
+ models.AutoField(
+ verbose_name="ID",
+ serialize=False,
+ auto_created=True,
+ primary_key=True,
+ ),
+ ),
+ ("primary", models.BooleanField(default=False)),
+ (
+ "avatar",
+ models.ImageField(
+ storage=django.core.files.storage.FileSystemStorage(),
+ max_length=1024,
+ upload_to=avatar.models.avatar_file_path,
+ blank=True,
+ ),
+ ),
+ (
+ "date_uploaded",
+ models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ (
+ "user",
+ models.ForeignKey(
+ to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
+ ),
+ ),
],
),
]
diff --git a/avatar/migrations/0002_add_verbose_names_to_avatar_fields.py b/avatar/migrations/0002_add_verbose_names_to_avatar_fields.py
index b0f71cb..4315b2d 100644
--- a/avatar/migrations/0002_add_verbose_names_to_avatar_fields.py
+++ b/avatar/migrations/0002_add_verbose_names_to_avatar_fields.py
@@ -9,32 +9,44 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
- ('avatar', '0001_initial'),
+ ("avatar", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
- name='avatar',
- options={'verbose_name': 'avatar', 'verbose_name_plural': 'avatars'},
+ name="avatar",
+ options={"verbose_name": "avatar", "verbose_name_plural": "avatars"},
),
migrations.AlterField(
- model_name='avatar',
- name='avatar',
- field=models.ImageField(blank=True, max_length=1024, storage=django.core.files.storage.FileSystemStorage(), upload_to=avatar.models.avatar_path_handler, verbose_name='avatar'),
+ model_name="avatar",
+ name="avatar",
+ field=models.ImageField(
+ blank=True,
+ max_length=1024,
+ storage=django.core.files.storage.FileSystemStorage(),
+ upload_to=avatar.models.avatar_path_handler,
+ verbose_name="avatar",
+ ),
),
migrations.AlterField(
- model_name='avatar',
- name='date_uploaded',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='uploaded at'),
+ model_name="avatar",
+ name="date_uploaded",
+ field=models.DateTimeField(
+ default=django.utils.timezone.now, verbose_name="uploaded at"
+ ),
),
migrations.AlterField(
- model_name='avatar',
- name='primary',
- field=models.BooleanField(default=False, verbose_name='primary'),
+ model_name="avatar",
+ name="primary",
+ field=models.BooleanField(default=False, verbose_name="primary"),
),
migrations.AlterField(
- model_name='avatar',
- name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user'),
+ model_name="avatar",
+ name="user",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="user",
+ ),
),
]
diff --git a/avatar/migrations/0003_auto_20170827_1345.py b/avatar/migrations/0003_auto_20170827_1345.py
index fa575f8..5e58509 100644
--- a/avatar/migrations/0003_auto_20170827_1345.py
+++ b/avatar/migrations/0003_auto_20170827_1345.py
@@ -5,13 +5,13 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('avatar', '0002_add_verbose_names_to_avatar_fields'),
+ ("avatar", "0002_add_verbose_names_to_avatar_fields"),
]
operations = [
migrations.AlterField(
- model_name='avatar',
- name='avatar',
+ model_name="avatar",
+ name="avatar",
field=avatar.models.AvatarField(),
),
]
diff --git a/avatar/models.py b/avatar/models.py
index 79babcd..52cf65c 100644
--- a/avatar/models.py
+++ b/avatar/models.py
@@ -46,12 +46,12 @@ def avatar_path_handler(instance=None, filename=None, size=None, ext=None):
if settings.AVATAR_HASH_FILENAMES:
(root, ext) = os.path.splitext(filename)
if settings.AVATAR_RANDOMIZE_HASHES:
- filename = binascii.hexlify(os.urandom(16)).decode('ascii')
+ filename = binascii.hexlify(os.urandom(16)).decode("ascii")
else:
filename = hashlib.md5(force_bytes(filename)).hexdigest()
filename = filename + ext
if size:
- tmppath.extend(['resized', str(size)])
+ tmppath.extend(["resized", str(size)])
tmppath.append(os.path.basename(filename))
return os.path.join(*tmppath)
@@ -62,14 +62,13 @@ avatar_file_path = import_string(settings.AVATAR_PATH_HANDLER)
def find_extension(format):
format = format.lower()
- if format == 'jpeg':
- format = 'jpg'
+ if format == "jpeg":
+ format = "jpg"
return format
class AvatarField(models.ImageField):
-
def __init__(self, *args, **kwargs):
super(AvatarField, self).__init__(*args, **kwargs)
@@ -85,28 +84,27 @@ class AvatarField(models.ImageField):
class Avatar(models.Model):
user = models.ForeignKey(
- getattr(settings, 'AUTH_USER_MODEL', 'auth.User'),
- verbose_name=_("user"), on_delete=models.CASCADE,
+ getattr(settings, "AUTH_USER_MODEL", "auth.User"),
+ verbose_name=_("user"),
+ on_delete=models.CASCADE,
)
primary = models.BooleanField(
verbose_name=_("primary"),
default=False,
)
- avatar = AvatarField(
- verbose_name=_("avatar")
- )
+ avatar = AvatarField(verbose_name=_("avatar"))
date_uploaded = models.DateTimeField(
verbose_name=_("uploaded at"),
default=now,
)
class Meta:
- app_label = 'avatar'
- verbose_name = _('avatar')
- verbose_name_plural = _('avatars')
+ app_label = "avatar"
+ verbose_name = _("avatar")
+ verbose_name_plural = _("avatars")
def __unicode__(self):
- return _(six.u('Avatar for %s')) % self.user
+ return _(six.u("Avatar for %s")) % self.user
def save(self, *args, **kwargs):
avatars = Avatar.objects.filter(user=self.user)
@@ -125,19 +123,19 @@ class Avatar(models.Model):
def transpose_image(self, image):
"""
- Transpose based on EXIF information.
- Borrowed from django-imagekit:
- imagekit.processors.Transpose
+ Transpose based on EXIF information.
+ Borrowed from django-imagekit:
+ imagekit.processors.Transpose
"""
EXIF_ORIENTATION_STEPS = {
1: [],
- 2: ['FLIP_LEFT_RIGHT'],
- 3: ['ROTATE_180'],
- 4: ['FLIP_TOP_BOTTOM'],
- 5: ['ROTATE_270', 'FLIP_LEFT_RIGHT'],
- 6: ['ROTATE_270'],
- 7: ['ROTATE_90', 'FLIP_LEFT_RIGHT'],
- 8: ['ROTATE_90'],
+ 2: ["FLIP_LEFT_RIGHT"],
+ 3: ["ROTATE_180"],
+ 4: ["FLIP_TOP_BOTTOM"],
+ 5: ["ROTATE_270", "FLIP_LEFT_RIGHT"],
+ 6: ["ROTATE_270"],
+ 7: ["ROTATE_90", "FLIP_LEFT_RIGHT"],
+ 8: ["ROTATE_90"],
}
try:
orientation = image._getexif()[0x0112]
@@ -152,9 +150,9 @@ class Avatar(models.Model):
# invalidate the cache of the thumbnail with the given size first
invalidate_cache(self.user, size)
try:
- orig = self.avatar.storage.open(self.avatar.name, 'rb')
+ orig = self.avatar.storage.open(self.avatar.name, "rb")
except IOError:
- return # What should we do here? Render a "sorry, didn't work" img?
+ return # What should we do here? Render a "sorry, didn't work" img?
try:
image = Image.open(orig)
image = self.transpose_image(image)
@@ -188,11 +186,7 @@ class Avatar(models.Model):
def avatar_name(self, size):
ext = find_extension(settings.AVATAR_THUMB_FORMAT)
- return avatar_file_path(
- instance=self,
- size=size,
- ext=ext
- )
+ return avatar_file_path(instance=self, size=size, ext=ext)
def invalidate_avatar_cache(sender, instance, **kwargs):
diff --git a/avatar/providers.py b/avatar/providers.py
index b74549c..7dfd1d9 100644
--- a/avatar/providers.py
+++ b/avatar/providers.py
@@ -16,7 +16,7 @@ from avatar.utils import (
# ``AVATAR_FACEBOOK_GET_ID``.
get_facebook_id = None
-if 'avatar.providers.FacebookAvatarProvider' in settings.AVATAR_PROVIDERS:
+if "avatar.providers.FacebookAvatarProvider" in settings.AVATAR_PROVIDERS:
if callable(settings.AVATAR_FACEBOOK_GET_ID):
get_facebook_id = settings.AVATAR_FACEBOOK_GET_ID
else:
@@ -52,13 +52,17 @@ class GravatarAvatarProvider(object):
@classmethod
def get_avatar_url(self, user, size):
- params = {'s': str(size)}
+ params = {"s": str(size)}
if settings.AVATAR_GRAVATAR_DEFAULT:
- params['d'] = 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(getattr(user,
- settings.AVATAR_GRAVATAR_FIELD))).hexdigest(), urlencode(params))
+ params["f"] = "y"
+ path = "%s/?%s" % (
+ hashlib.md5(
+ force_bytes(getattr(user, settings.AVATAR_GRAVATAR_FIELD))
+ ).hexdigest(),
+ urlencode(params),
+ )
return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path)
@@ -72,8 +76,5 @@ class FacebookAvatarProvider(object):
def get_avatar_url(self, user, size):
fb_id = get_facebook_id(user)
if fb_id:
- url = 'https://graph.facebook.com/{fb_id}/picture?type=square&width={size}&height={size}'
- return url.format(
- fb_id=fb_id,
- size=size
- )
+ url = "https://graph.facebook.com/{fb_id}/picture?type=square&width={size}&height={size}"
+ return url.format(fb_id=fb_id, size=size)
diff --git a/avatar/templatetags/avatar_tags.py b/avatar/templatetags/avatar_tags.py
index 707cd3d..a234e2f 100644
--- a/avatar/templatetags/avatar_tags.py
+++ b/avatar/templatetags/avatar_tags.py
@@ -44,15 +44,15 @@ def avatar(user, size=settings.AVATAR_DEFAULT_SIZE, **kwargs):
else:
alt = six.text_type(user)
url = avatar_url(user, size)
- kwargs.update({'alt': alt})
+ kwargs.update({"alt": alt})
context = {
- 'user': user,
- 'url': url,
- 'size': size,
- 'kwargs': kwargs,
+ "user": user,
+ "url": url,
+ "size": size,
+ "kwargs": kwargs,
}
- return render_to_string('avatar/avatar_tag.html', context)
+ return render_to_string("avatar/avatar_tag.html", context)
@register.filter
@@ -72,9 +72,13 @@ def primary_avatar(user, size=settings.AVATAR_DEFAULT_SIZE):
we will avoid many db calls.
"""
alt = six.text_type(user)
- url = reverse('avatar_render_primary', kwargs={'user': user, 'size': size})
- return ("""
""" %
- (url, alt, size, size))
+ url = reverse("avatar_render_primary", kwargs={"user": user, "size": size})
+ return """
""" % (
+ url,
+ alt,
+ size,
+ size,
+ )
@cache_result()
@@ -83,7 +87,11 @@ def render_avatar(avatar, size=settings.AVATAR_DEFAULT_SIZE):
if not avatar.thumbnail_exists(size):
avatar.create_thumbnail(size)
return """
""" % (
- avatar.avatar_url(size), six.text_type(avatar), size, size)
+ avatar.avatar_url(size),
+ six.text_type(avatar),
+ size,
+ size,
+ )
@register.tag
@@ -91,8 +99,7 @@ def primary_avatar_object(parser, token):
split = token.split_contents()
if len(split) == 4:
return UsersAvatarObjectNode(split[1], split[3])
- raise template.TemplateSyntaxError('%r tag takes three arguments.' %
- split[0])
+ raise template.TemplateSyntaxError("%r tag takes three arguments." % split[0])
class UsersAvatarObjectNode(template.Node):
diff --git a/avatar/urls.py b/avatar/urls.py
index 379158e..a286158 100644
--- a/avatar/urls.py
+++ b/avatar/urls.py
@@ -3,10 +3,12 @@ from django.urls import re_path
from avatar import views
urlpatterns = [
- re_path(r'^add/$', views.add, name='avatar_add'),
- re_path(r'^change/$', views.change, name='avatar_change'),
- re_path(r'^delete/$', views.delete, name='avatar_delete'),
- re_path(r'^render_primary/(?P[\w\d\@\.\-_]+)/(?P[\d]+)/$',
+ re_path(r"^add/$", views.add, name="avatar_add"),
+ re_path(r"^change/$", views.change, name="avatar_change"),
+ re_path(r"^delete/$", views.delete, name="avatar_delete"),
+ re_path(
+ r"^render_primary/(?P[\w\d\@\.\-_]+)/(?P[\d]+)/$",
views.render_primary,
- name='avatar_render_primary'),
+ name="avatar_render_primary",
+ ),
]
diff --git a/avatar/utils.py b/avatar/utils.py
index 2882283..6660fc8 100644
--- a/avatar/utils.py
+++ b/avatar/utils.py
@@ -14,7 +14,7 @@ cached_funcs = set()
def get_username(user):
""" Return username of a User instance """
- if hasattr(user, 'get_username'):
+ if hasattr(user, "get_username"):
return user.get_username()
else:
return user.username
@@ -31,9 +31,11 @@ def get_cache_key(user_or_username, size, prefix):
"""
if isinstance(user_or_username, get_user_model()):
user_or_username = get_username(user_or_username)
- key = six.u('%s_%s_%s') % (prefix, user_or_username, size)
- return six.u('%s_%s') % (slugify(key)[:100],
- hashlib.md5(force_bytes(key)).hexdigest())
+ key = six.u("%s_%s_%s") % (prefix, user_or_username, size)
+ return six.u("%s_%s") % (
+ slugify(key)[:100],
+ hashlib.md5(force_bytes(key)).hexdigest(),
+ )
def cache_set(key, value):
@@ -47,8 +49,10 @@ def cache_result(default_size=settings.AVATAR_DEFAULT_SIZE):
``size`` value.
"""
if not settings.AVATAR_CACHE_ENABLED:
+
def decorator(func):
return func
+
return decorator
def decorator(func):
@@ -61,7 +65,9 @@ def cache_result(default_size=settings.AVATAR_DEFAULT_SIZE):
result = func(user, size or default_size, **kwargs)
cache_set(key, result)
return result
+
return cached_func
+
return decorator
@@ -78,23 +84,23 @@ def invalidate_cache(user, size=None):
def get_default_avatar_url():
- base_url = getattr(settings, 'STATIC_URL', None)
+ base_url = getattr(settings, "STATIC_URL", None)
if not base_url:
- base_url = getattr(settings, 'MEDIA_URL', '')
+ base_url = getattr(settings, "MEDIA_URL", "")
# Don't use base_url if the default url starts with http:// of https://
- if settings.AVATAR_DEFAULT_URL.startswith(('http://', 'https://')):
+ if settings.AVATAR_DEFAULT_URL.startswith(("http://", "https://")):
return settings.AVATAR_DEFAULT_URL
# We'll be nice and make sure there are no duplicated forward slashes
- ends = base_url.endswith('/')
+ ends = base_url.endswith("/")
- begins = settings.AVATAR_DEFAULT_URL.startswith('/')
+ begins = settings.AVATAR_DEFAULT_URL.startswith("/")
if ends and begins:
base_url = base_url[:-1]
elif not ends and not begins:
- return '%s/%s' % (base_url, settings.AVATAR_DEFAULT_URL)
+ return "%s/%s" % (base_url, settings.AVATAR_DEFAULT_URL)
- return '%s%s' % (base_url, settings.AVATAR_DEFAULT_URL)
+ return "%s%s" % (base_url, settings.AVATAR_DEFAULT_URL)
def get_primary_avatar(user, size=settings.AVATAR_DEFAULT_SIZE):
diff --git a/avatar/views.py b/avatar/views.py
index ec2619e..72b4fd5 100644
--- a/avatar/views.py
+++ b/avatar/views.py
@@ -9,8 +9,7 @@ from avatar.conf import settings
from avatar.forms import PrimaryAvatarForm, DeleteAvatarForm, UploadAvatarForm
from avatar.models import Avatar
from avatar.signals import avatar_updated, avatar_deleted
-from avatar.utils import (get_primary_avatar, get_default_avatar_url,
- invalidate_cache)
+from avatar.utils import get_primary_avatar, get_default_avatar_url, invalidate_cache
def _get_next(request):
@@ -28,8 +27,9 @@ def _get_next(request):
3. If Django can determine the previous page from the HTTP headers,
the view will redirect to that previous page.
"""
- next = request.POST.get('next', request.GET.get('next',
- request.META.get('HTTP_REFERER', None)))
+ next = request.POST.get(
+ "next", request.GET.get("next", request.META.get("HTTP_REFERER", None))
+ )
if not next:
next = request.path
return next
@@ -40,7 +40,7 @@ def _get_avatars(user):
avatars = user.avatar_set.all()
# Current avatar
- primary_avatar = avatars.order_by('-primary')[:1]
+ primary_avatar = avatars.order_by("-primary")[:1]
if primary_avatar:
avatar = primary_avatar[0]
else:
@@ -51,59 +51,70 @@ def _get_avatars(user):
else:
# Slice the default set now that we used
# the queryset for the primary avatar
- avatars = avatars[:settings.AVATAR_MAX_AVATARS_PER_USER]
+ avatars = avatars[: settings.AVATAR_MAX_AVATARS_PER_USER]
return (avatar, avatars)
@login_required
-def add(request, extra_context=None, next_override=None,
- upload_form=UploadAvatarForm, *args, **kwargs):
+def add(
+ request,
+ extra_context=None,
+ next_override=None,
+ upload_form=UploadAvatarForm,
+ *args,
+ **kwargs
+):
if extra_context is None:
extra_context = {}
avatar, avatars = _get_avatars(request.user)
- upload_avatar_form = upload_form(request.POST or None,
- request.FILES or None,
- user=request.user)
- if request.method == "POST" and 'avatar' in request.FILES:
+ upload_avatar_form = upload_form(
+ request.POST or None, request.FILES or None, user=request.user
+ )
+ if request.method == "POST" and "avatar" in request.FILES:
if upload_avatar_form.is_valid():
avatar = Avatar(user=request.user, primary=True)
- image_file = request.FILES['avatar']
+ image_file = request.FILES["avatar"]
avatar.avatar.save(image_file.name, image_file)
avatar.save()
messages.success(request, _("Successfully uploaded a new avatar."))
avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar)
return redirect(next_override or _get_next(request))
context = {
- 'avatar': avatar,
- 'avatars': avatars,
- 'upload_avatar_form': upload_avatar_form,
- 'next': next_override or _get_next(request),
+ "avatar": avatar,
+ "avatars": avatars,
+ "upload_avatar_form": upload_avatar_form,
+ "next": next_override or _get_next(request),
}
context.update(extra_context)
- template_name = settings.AVATAR_ADD_TEMPLATE or 'avatar/add.html'
+ template_name = settings.AVATAR_ADD_TEMPLATE or "avatar/add.html"
return render(request, template_name, context)
@login_required
-def change(request, extra_context=None, next_override=None,
- upload_form=UploadAvatarForm, primary_form=PrimaryAvatarForm,
- *args, **kwargs):
+def change(
+ request,
+ extra_context=None,
+ next_override=None,
+ upload_form=UploadAvatarForm,
+ primary_form=PrimaryAvatarForm,
+ *args,
+ **kwargs
+):
if extra_context is None:
extra_context = {}
avatar, avatars = _get_avatars(request.user)
if avatar:
- kwargs = {'initial': {'choice': avatar.id}}
+ kwargs = {"initial": {"choice": avatar.id}}
else:
kwargs = {}
upload_avatar_form = upload_form(user=request.user, **kwargs)
- primary_avatar_form = primary_form(request.POST or None,
- user=request.user,
- avatars=avatars, **kwargs)
+ primary_avatar_form = primary_form(
+ request.POST or None, user=request.user, avatars=avatars, **kwargs
+ )
if request.method == "POST":
updated = False
- if 'choice' in request.POST and primary_avatar_form.is_valid():
- avatar = Avatar.objects.get(
- id=primary_avatar_form.cleaned_data['choice'])
+ if "choice" in request.POST and primary_avatar_form.is_valid():
+ avatar = Avatar.objects.get(id=primary_avatar_form.cleaned_data["choice"])
avatar.primary = True
avatar.save()
updated = True
@@ -114,14 +125,14 @@ def change(request, extra_context=None, next_override=None,
return redirect(next_override or _get_next(request))
context = {
- 'avatar': avatar,
- 'avatars': avatars,
- 'upload_avatar_form': upload_avatar_form,
- 'primary_avatar_form': primary_avatar_form,
- 'next': next_override or _get_next(request)
+ "avatar": avatar,
+ "avatars": avatars,
+ "upload_avatar_form": upload_avatar_form,
+ "primary_avatar_form": primary_avatar_form,
+ "next": next_override or _get_next(request),
}
context.update(extra_context)
- template_name = settings.AVATAR_CHANGE_TEMPLATE or 'avatar/change.html'
+ template_name = settings.AVATAR_CHANGE_TEMPLATE or "avatar/change.html"
return render(request, template_name, context)
@@ -130,38 +141,37 @@ def delete(request, extra_context=None, next_override=None, *args, **kwargs):
if extra_context is None:
extra_context = {}
avatar, avatars = _get_avatars(request.user)
- delete_avatar_form = DeleteAvatarForm(request.POST or None,
- user=request.user,
- avatars=avatars)
- if request.method == 'POST':
+ delete_avatar_form = DeleteAvatarForm(
+ request.POST or None, user=request.user, avatars=avatars
+ )
+ if request.method == "POST":
if delete_avatar_form.is_valid():
- ids = delete_avatar_form.cleaned_data['choices']
+ ids = delete_avatar_form.cleaned_data["choices"]
for a in avatars:
if six.text_type(a.id) in ids:
- avatar_deleted.send(sender=Avatar, user=request.user,
- avatar=a)
+ avatar_deleted.send(sender=Avatar, user=request.user, avatar=a)
if six.text_type(avatar.id) in ids and avatars.count() > len(ids):
# Find the next best avatar, and set it as the new primary
for a in avatars:
if six.text_type(a.id) not in ids:
a.primary = True
a.save()
- avatar_updated.send(sender=Avatar, user=request.user,
- avatar=avatar)
+ avatar_updated.send(
+ sender=Avatar, user=request.user, avatar=avatar
+ )
break
Avatar.objects.filter(id__in=ids).delete()
- messages.success(request,
- _("Successfully deleted the requested avatars."))
+ messages.success(request, _("Successfully deleted the requested avatars."))
return redirect(next_override or _get_next(request))
context = {
- 'avatar': avatar,
- 'avatars': avatars,
- 'delete_avatar_form': delete_avatar_form,
- 'next': next_override or _get_next(request),
+ "avatar": avatar,
+ "avatars": avatars,
+ "delete_avatar_form": delete_avatar_form,
+ "next": next_override or _get_next(request),
}
context.update(extra_context)
- template_name = settings.AVATAR_DELETE_TEMPLATE or 'avatar/confirm_delete.html'
+ template_name = settings.AVATAR_DELETE_TEMPLATE or "avatar/confirm_delete.html"
return render(request, template_name, context)
diff --git a/docs/conf.py b/docs/conf.py
index 9caf686..cf9fde9 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,199 +16,202 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath("."))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix of source filenames.
-source_suffix = '.txt'
+source_suffix = ".txt"
# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'django-avatar'
-copyright = u'2013, django-avatar developers'
+project = u"django-avatar"
+copyright = u"2013, django-avatar developers"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '2.0'
+version = "2.0"
# The full version, including alpha/beta/rc tags.
-release = '2.0'
+release = "2.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
-#language = None
+# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
-#today = ''
+# today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
+exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
+# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
-#show_authors = False
+# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
-#html_title = None
+# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-#html_favicon = None
+# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
# If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
# If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
# If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
# Output file base name for HTML help builder.
-htmlhelp_basename = 'django-avatardoc'
+htmlhelp_basename = "django-avatardoc"
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+ # The paper size ('letterpaper' or 'a4paper').
+ #'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
- ('index', 'django-avatar.tex', u'django-avatar Documentation',
- u'django-avatar developers', 'manual'),
+ (
+ "index",
+ "django-avatar.tex",
+ u"django-avatar Documentation",
+ u"django-avatar developers",
+ "manual",
+ ),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
-#latex_logo = None
+# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
# If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
# If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
# Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
# If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@@ -216,12 +219,17 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'django-avatar', u'django-avatar Documentation',
- [u'django-avatar developers'], 1)
+ (
+ "index",
+ "django-avatar",
+ u"django-avatar Documentation",
+ [u"django-avatar developers"],
+ 1,
+ )
]
# If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
@@ -230,19 +238,25 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- ('index', 'django-avatar', u'django-avatar Documentation',
- u'django-avatar developers', 'django-avatar', 'One line description of project.',
- 'Miscellaneous'),
+ (
+ "index",
+ "django-avatar",
+ u"django-avatar Documentation",
+ u"django-avatar developers",
+ "django-avatar",
+ "One line description of project.",
+ "Miscellaneous",
+ ),
]
# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
# If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
diff --git a/setup.py b/setup.py
index 7fc17d3..ceb977f 100644
--- a/setup.py
+++ b/setup.py
@@ -6,68 +6,67 @@ from setuptools import setup, find_packages
def read(*parts):
filename = path.join(path.dirname(__file__), *parts)
- with codecs.open(filename, encoding='utf-8') as fp:
+ with codecs.open(filename, encoding="utf-8") as fp:
return fp.read()
def find_version(*file_paths):
version_file = read(*file_paths)
- version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
- version_file, re.M)
+ version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
setup(
- name='django-avatar',
+ name="django-avatar",
version=find_version("avatar", "__init__.py"),
description="A Django app for handling user avatars",
- long_description=read('README.rst'),
+ long_description=read("README.rst"),
classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Web Environment',
- 'Framework :: Django',
- 'Intended Audience :: Developers',
- 'Framework :: Django',
- 'Framework :: Django :: 1.11',
- 'Framework :: Django :: 2.0',
- 'Framework :: Django :: 2.1',
- 'Framework :: Django :: 2.2',
- 'Framework :: Django :: 3.0',
- 'Framework :: Django :: 4.0',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Web Environment",
+ "Framework :: Django",
+ "Intended Audience :: Developers",
+ "Framework :: Django",
+ "Framework :: Django :: 1.11",
+ "Framework :: Django :: 2.0",
+ "Framework :: Django :: 2.1",
+ "Framework :: Django :: 2.2",
+ "Framework :: Django :: 3.0",
+ "Framework :: Django :: 4.0",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
],
- keywords='avatar, django',
- author='Eric Florenzano',
- author_email='floguy@gmail.com',
- maintainer='Grant McConnaughey',
- maintainer_email='grantmcconnaughey@gmail.com',
- url='http://github.com/grantmcconnaughey/django-avatar/',
- license='BSD',
- packages=find_packages(exclude=['tests']),
+ keywords="avatar, django",
+ author="Eric Florenzano",
+ author_email="floguy@gmail.com",
+ maintainer="Grant McConnaughey",
+ maintainer_email="grantmcconnaughey@gmail.com",
+ url="http://github.com/grantmcconnaughey/django-avatar/",
+ license="BSD",
+ packages=find_packages(exclude=["tests"]),
package_data={
- 'avatar': [
- 'templates/notification/*/*.*',
- 'templates/avatar/*.html',
- 'locale/*/LC_MESSAGES/*',
- 'media/avatar/img/default.jpg',
+ "avatar": [
+ "templates/notification/*/*.*",
+ "templates/avatar/*.html",
+ "locale/*/LC_MESSAGES/*",
+ "media/avatar/img/default.jpg",
],
},
install_requires=[
- 'Pillow>=2.0',
- 'django-appconf>=0.6',
+ "Pillow>=2.0",
+ "django-appconf>=0.6",
],
zip_safe=False,
)
diff --git a/test_proj/manage.py b/test_proj/manage.py
index d61e194..912417f 100755
--- a/test_proj/manage.py
+++ b/test_proj/manage.py
@@ -7,7 +7,7 @@ if __name__ == "__main__":
# Add the django-avatar directory to the Python path. That way the
# avatar module can be imported.
- sys.path.append('..')
+ sys.path.append("..")
try:
from django.core.management import execute_from_command_line
except ImportError:
diff --git a/test_proj/test_proj/settings.py b/test_proj/test_proj/settings.py
index 81638d8..cd24a2b 100644
--- a/test_proj/test_proj/settings.py
+++ b/test_proj/test_proj/settings.py
@@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = '0o$jym8^hgw%vwx9hy%@ncr!29n7gik30(ln$pd$!3*4zu+9dv'
+SECRET_KEY = "0o$jym8^hgw%vwx9hy%@ncr!29n7gik30(ln$pd$!3*4zu+9dv"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@@ -31,54 +31,53 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
-
- 'avatar',
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "avatar",
]
MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
-ROOT_URLCONF = 'test_proj.urls'
+ROOT_URLCONF = "test_proj.urls"
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
],
},
},
]
-WSGI_APPLICATION = 'test_proj.wsgi.application'
+WSGI_APPLICATION = "test_proj.wsgi.application"
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
@@ -86,9 +85,9 @@ DATABASES = {
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
-LANGUAGE_CODE = 'en-us'
+LANGUAGE_CODE = "en-us"
-TIME_ZONE = 'UTC'
+TIME_ZONE = "UTC"
USE_I18N = True
@@ -100,7 +99,7 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
-STATIC_URL = '/static/'
+STATIC_URL = "/static/"
-MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
-MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, "media")
+MEDIA_URL = "/media/"
diff --git a/test_proj/test_proj/urls.py b/test_proj/test_proj/urls.py
index 1144ca1..c80c7e5 100644
--- a/test_proj/test_proj/urls.py
+++ b/test_proj/test_proj/urls.py
@@ -4,14 +4,13 @@ from django.contrib import admin
from django.views.static import serve
urlpatterns = [
- url(r'^admin/', admin.site.urls),
- url(r'^avatar/', include('avatar.urls')),
+ url(r"^admin/", admin.site.urls),
+ url(r"^avatar/", include("avatar.urls")),
]
if settings.DEBUG:
# static files (images, css, javascript, etc.)
urlpatterns += [
- url(r'^media/(?P.*)$', serve, {
- 'document_root': settings.MEDIA_ROOT})
+ url(r"^media/(?P.*)$", serve, {"document_root": settings.MEDIA_ROOT})
]
diff --git a/tests/settings.py b/tests/settings.py
index 8c66dee..cce910d 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -2,23 +2,23 @@ import os
SETTINGS_DIR = os.path.dirname(__file__)
-DATABASE_ENGINE = 'sqlite3'
+DATABASE_ENGINE = "sqlite3"
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': ':memory:',
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": ":memory:",
}
}
INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.messages',
- 'django.contrib.sessions',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sites',
- 'avatar',
+ "django.contrib.admin",
+ "django.contrib.messages",
+ "django.contrib.sessions",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sites",
+ "avatar",
]
MIDDLEWARE = (
@@ -31,30 +31,28 @@ MIDDLEWARE = (
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'APP_DIRS': True,
- 'DIRS': [
- os.path.join(SETTINGS_DIR, 'templates')
- ],
- 'OPTIONS': {
- 'context_processors': [
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages'
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "APP_DIRS": True,
+ "DIRS": [os.path.join(SETTINGS_DIR, "templates")],
+ "OPTIONS": {
+ "context_processors": [
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
]
- }
+ },
}
]
-ROOT_URLCONF = 'tests.urls'
+ROOT_URLCONF = "tests.urls"
SITE_ID = 1
-SECRET_KEY = 'something-something'
+SECRET_KEY = "something-something"
-ROOT_URLCONF = 'tests.urls'
+ROOT_URLCONF = "tests.urls"
-STATIC_URL = '/site_media/static/'
+STATIC_URL = "/site_media/static/"
-AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png')
+AVATAR_ALLOWED_FILE_EXTS = (".jpg", ".png")
AVATAR_MAX_SIZE = 1024 * 1024
AVATAR_MAX_AVATARS_PER_USER = 20
diff --git a/tests/tests.py b/tests/tests.py
index 4466247..d9c592f 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -3,6 +3,7 @@ import os.path
import math
from django.contrib.admin.sites import AdminSite
from django.test import TestCase
+
try:
from django.urls import reverse
except ImportError:
@@ -37,16 +38,20 @@ class AssertSignal:
def upload_helper(o, filename):
f = open(os.path.join(o.testdatapath, filename), "rb")
- response = o.client.post(reverse('avatar_add'), {
- 'avatar': f,
- }, follow=True)
+ response = o.client.post(
+ reverse("avatar_add"),
+ {
+ "avatar": f,
+ },
+ follow=True,
+ )
f.close()
return response
def root_mean_square_difference(image1, image2):
"Calculate the root-mean-square difference between two images"
- diff = ImageChops.difference(image1, image2).convert('L')
+ diff = ImageChops.difference(image1, image2).convert("L")
h = diff.histogram()
sq = (value * (idx ** 2) for idx, value in enumerate(h))
sum_of_squares = sum(sq)
@@ -57,9 +62,11 @@ def root_mean_square_difference(image1, image2):
class AvatarTests(TestCase):
def setUp(self):
self.testdatapath = os.path.join(os.path.dirname(__file__), "data")
- self.user = get_user_model().objects.create_user('test', 'lennon@thebeatles.com', 'testpassword')
+ self.user = get_user_model().objects.create_user(
+ "test", "lennon@thebeatles.com", "testpassword"
+ )
self.user.save()
- self.client.login(username='test', password='testpassword')
+ self.client.login(username="test", password="testpassword")
self.site = AdminSite()
Image.init()
@@ -78,13 +85,13 @@ class AvatarTests(TestCase):
def test_non_image_upload(self):
response = upload_helper(self, "nonimagefile")
self.assertEqual(response.status_code, 200)
- self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
+ self.assertNotEqual(response.context["upload_avatar_form"].errors, {})
def test_normal_image_upload(self):
response = upload_helper(self, "test.png")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 1)
- self.assertEqual(response.context['upload_avatar_form'].errors, {})
+ self.assertEqual(response.context["upload_avatar_form"].errors, {})
avatar = get_primary_avatar(self.user)
self.assertIsNotNone(avatar)
self.assertEqual(avatar.user, self.user)
@@ -95,29 +102,34 @@ class AvatarTests(TestCase):
response = upload_helper(self, "imagefilewithoutext")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
- self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
+ self.assertNotEqual(response.context["upload_avatar_form"].errors, {})
def test_image_with_wrong_extension(self):
# use with AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png')
response = upload_helper(self, "imagefilewithwrongext.ogg")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
- self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
+ self.assertNotEqual(response.context["upload_avatar_form"].errors, {})
def test_image_too_big(self):
# use with AVATAR_MAX_SIZE = 1024 * 1024
response = upload_helper(self, "testbig.png")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
- self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
+ self.assertNotEqual(response.context["upload_avatar_form"].errors, {})
def test_default_url(self):
- response = self.client.get(reverse('avatar_render_primary', kwargs={
- 'user': self.user.username,
- 'size': 80,
- }))
- loc = response['Location']
- base_url = getattr(settings, 'STATIC_URL', None)
+ response = self.client.get(
+ reverse(
+ "avatar_render_primary",
+ kwargs={
+ "user": self.user.username,
+ "size": 80,
+ },
+ )
+ )
+ loc = response["Location"]
+ base_url = getattr(settings, "STATIC_URL", None)
if not base_url:
base_url = settings.MEDIA_URL
self.assertTrue(base_url in loc)
@@ -139,9 +151,13 @@ class AvatarTests(TestCase):
self.assertEqual(len(avatar), 1)
receiver = AssertSignal()
avatar_deleted.connect(receiver)
- response = self.client.post(reverse('avatar_delete'), {
- 'choices': [avatar[0].id],
- }, follow=True)
+ response = self.client.post(
+ reverse("avatar_delete"),
+ {
+ "choices": [avatar[0].id],
+ },
+ follow=True,
+ )
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 1)
count = Avatar.objects.filter(user=self.user).count()
@@ -155,9 +171,12 @@ class AvatarTests(TestCase):
self.test_there_can_be_only_one_primary_avatar()
primary = get_primary_avatar(self.user)
oid = primary.id
- self.client.post(reverse('avatar_delete'), {
- 'choices': [oid],
- })
+ self.client.post(
+ reverse("avatar_delete"),
+ {
+ "choices": [oid],
+ },
+ )
primaries = Avatar.objects.filter(user=self.user, primary=True)
self.assertEqual(len(primaries), 1)
self.assertNotEqual(oid, primaries[0].id)
@@ -166,24 +185,31 @@ class AvatarTests(TestCase):
def test_change_avatar_get(self):
self.test_normal_image_upload()
- response = self.client.get(reverse('avatar_change'))
+ response = self.client.get(reverse("avatar_change"))
self.assertEqual(response.status_code, 200)
- self.assertIsNotNone(response.context['avatar'])
+ self.assertIsNotNone(response.context["avatar"])
def test_change_avatar_post_updates_primary_avatar(self):
self.test_there_can_be_only_one_primary_avatar()
old_primary = Avatar.objects.get(user=self.user, primary=True)
choice = Avatar.objects.filter(user=self.user, primary=False)[0]
- response = self.client.post(reverse('avatar_change'), {
- 'choice': choice.pk,
- })
+ response = self.client.post(
+ reverse("avatar_change"),
+ {
+ "choice": choice.pk,
+ },
+ )
self.assertEqual(response.status_code, 302)
new_primary = Avatar.objects.get(user=self.user, primary=True)
self.assertEqual(new_primary.pk, choice.pk)
# Avatar with old primary pk exists but it is not primary anymore
- self.assertTrue(Avatar.objects.filter(user=self.user, pk=old_primary.pk, primary=False).exists())
+ self.assertTrue(
+ Avatar.objects.filter(
+ user=self.user, pk=old_primary.pk, primary=False
+ ).exists()
+ )
def test_too_many_avatars(self):
for i in range(0, settings.AVATAR_MAX_AVATARS_PER_USER):
@@ -193,30 +219,46 @@ class AvatarTests(TestCase):
count_after = Avatar.objects.filter(user=self.user).count()
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
- self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
+ self.assertNotEqual(response.context["upload_avatar_form"].errors, {})
self.assertEqual(count_before, count_after)
- @override_settings(AVATAR_THUMB_FORMAT='png')
+ @override_settings(AVATAR_THUMB_FORMAT="png")
def test_automatic_thumbnail_creation_RGBA(self):
upload_helper(self, "django.png")
avatar = get_primary_avatar(self.user)
- image = Image.open(avatar.avatar.storage.open(avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), 'rb'))
- self.assertEqual(image.mode, 'RGBA')
+ image = Image.open(
+ avatar.avatar.storage.open(
+ avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), "rb"
+ )
+ )
+ self.assertEqual(image.mode, "RGBA")
def test_automatic_thumbnail_creation_CMYK(self):
upload_helper(self, "django_pony_cmyk.jpg")
avatar = get_primary_avatar(self.user)
- image = Image.open(avatar.avatar.storage.open(avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), 'rb'))
- self.assertEqual(image.mode, 'RGB')
+ image = Image.open(
+ avatar.avatar.storage.open(
+ avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), "rb"
+ )
+ )
+ self.assertEqual(image.mode, "RGB")
def test_thumbnail_transpose_based_on_exif(self):
upload_helper(self, "image_no_exif.jpg")
avatar = get_primary_avatar(self.user)
- image_no_exif = Image.open(avatar.avatar.storage.open(avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), 'rb'))
+ image_no_exif = Image.open(
+ avatar.avatar.storage.open(
+ avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), "rb"
+ )
+ )
upload_helper(self, "image_exif_orientation.jpg")
avatar = get_primary_avatar(self.user)
- image_with_exif = Image.open(avatar.avatar.storage.open(avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), 'rb'))
+ image_with_exif = Image.open(
+ avatar.avatar.storage.open(
+ avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), "rb"
+ )
+ )
self.assertLess(root_mean_square_difference(image_with_exif, image_no_exif), 1)
@@ -263,41 +305,45 @@ class AvatarTests(TestCase):
avatar = get_primary_avatar(self.user)
result = avatar_tags.avatar(self.user, title="Avatar")
- html = '
'.format(avatar.avatar_url(80))
+ html = (
+ '
'.format(
+ avatar.avatar_url(80)
+ )
+ )
self.assertInHTML(html, result)
def test_default_add_template(self):
- response = self.client.get('/avatar/add/')
- self.assertContains(response, 'Upload New Image')
- self.assertNotContains(response, 'ALTERNATE ADD TEMPLATE')
+ response = self.client.get("/avatar/add/")
+ self.assertContains(response, "Upload New Image")
+ self.assertNotContains(response, "ALTERNATE ADD TEMPLATE")
- @override_settings(AVATAR_ADD_TEMPLATE='alt/add.html')
+ @override_settings(AVATAR_ADD_TEMPLATE="alt/add.html")
def test_custom_add_template(self):
- response = self.client.get('/avatar/add/')
- self.assertNotContains(response, 'Upload New Image')
- self.assertContains(response, 'ALTERNATE ADD TEMPLATE')
+ response = self.client.get("/avatar/add/")
+ self.assertNotContains(response, "Upload New Image")
+ self.assertContains(response, "ALTERNATE ADD TEMPLATE")
def test_default_change_template(self):
- response = self.client.get('/avatar/change/')
- self.assertContains(response, 'Upload New Image')
- self.assertNotContains(response, 'ALTERNATE CHANGE TEMPLATE')
+ response = self.client.get("/avatar/change/")
+ self.assertContains(response, "Upload New Image")
+ self.assertNotContains(response, "ALTERNATE CHANGE TEMPLATE")
- @override_settings(AVATAR_CHANGE_TEMPLATE='alt/change.html')
+ @override_settings(AVATAR_CHANGE_TEMPLATE="alt/change.html")
def test_custom_change_template(self):
- response = self.client.get('/avatar/change/')
- self.assertNotContains(response, 'Upload New Image')
- self.assertContains(response, 'ALTERNATE CHANGE TEMPLATE')
+ response = self.client.get("/avatar/change/")
+ self.assertNotContains(response, "Upload New Image")
+ self.assertContains(response, "ALTERNATE CHANGE TEMPLATE")
def test_default_delete_template(self):
- response = self.client.get('/avatar/delete/')
- self.assertContains(response, 'like to delete.')
- self.assertNotContains(response, 'ALTERNATE DELETE TEMPLATE')
+ response = self.client.get("/avatar/delete/")
+ self.assertContains(response, "like to delete.")
+ self.assertNotContains(response, "ALTERNATE DELETE TEMPLATE")
- @override_settings(AVATAR_DELETE_TEMPLATE='alt/delete.html')
+ @override_settings(AVATAR_DELETE_TEMPLATE="alt/delete.html")
def test_custom_delete_template(self):
- response = self.client.get('/avatar/delete/')
- self.assertNotContains(response, 'like to delete.')
- self.assertContains(response, 'ALTERNATE DELETE TEMPLATE')
+ response = self.client.get("/avatar/delete/")
+ self.assertNotContains(response, "like to delete.")
+ self.assertContains(response, "ALTERNATE DELETE TEMPLATE")
# def testAvatarOrder
# def testReplaceAvatarWhenMaxIsOne
diff --git a/tests/urls.py b/tests/urls.py
index ae590d0..ad82ab7 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -2,5 +2,5 @@ from django.conf.urls import include, url
urlpatterns = [
- url(r'^avatar/', include('avatar.urls')),
+ url(r"^avatar/", include("avatar.urls")),
]