From ae950c9b5074a013a4e04a6cdb44e65abaf023df Mon Sep 17 00:00:00 2001 From: Johannes Wilm Date: Sat, 16 Jul 2022 22:50:05 +0200 Subject: [PATCH] black --- avatar/__init__.py | 2 +- avatar/admin.py | 26 +-- avatar/apps.py | 4 +- avatar/conf.py | 29 ++-- avatar/forms.py | 82 +++++---- avatar/management/commands/rebuild_avatars.py | 8 +- avatar/migrations/0001_initial.py | 36 +++- ...0002_add_verbose_names_to_avatar_fields.py | 42 +++-- avatar/migrations/0003_auto_20170827_1345.py | 6 +- avatar/models.py | 56 +++--- avatar/providers.py | 23 +-- avatar/templatetags/avatar_tags.py | 31 ++-- avatar/urls.py | 12 +- avatar/utils.py | 28 +-- avatar/views.py | 110 ++++++------ docs/conf.py | 150 ++++++++-------- setup.py | 85 +++++---- test_proj/manage.py | 2 +- test_proj/test_proj/settings.py | 69 ++++---- test_proj/test_proj/urls.py | 7 +- tests/settings.py | 50 +++--- tests/tests.py | 164 +++++++++++------- tests/urls.py | 2 +- 23 files changed, 577 insertions(+), 447 deletions(-) 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('%s' % - (avatar.avatar_url(size), six.text_type(avatar), - size, size)) + return mark_safe( + '%s' + % (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 ("""%s""" % - (url, alt, size, size)) + url = reverse("avatar_render_primary", kwargs={"user": user, "size": size}) + return """%s""" % ( + 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 """%s""" % ( - 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 = 'test'.format(avatar.avatar_url(80)) + html = ( + 'test'.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")), ]