From 8034665e6b5111fbd70cba432f545bbf47caddbc Mon Sep 17 00:00:00 2001 From: 0xMRTT <0xMRTT@proton.me> Date: Wed, 22 Feb 2023 19:38:07 +0100 Subject: [PATCH] Add LibRavatar support (#114) (#225) * feat: add libravatar support (#114) and format * feat: add documentation for libravatar * fix(gh-actions): remove support for 3.6 and add 3.11 3.6 reached EOL: https://devguide.python.org/versions/ I also refactored the matrix by removing duplicated code * fix(deps): add missing dnspython * feat(deps): add requirements.txt * fix(gh-actions): install deps * chore(deps): add pyproject.toml See https://github.com/pypa/pip/issues/8559 * fix(gh-actions): add fail-fast so all checks run * fix(deps): bump coverage to 7.1.0 * fix(pre-commit): update versions * fix(pre-commit): config error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * style: update code for passing flake * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: 0xMRTT <0xMRTT@evta.fr> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 26 ++-------- .pre-commit-config.yaml | 8 +-- avatar/conf.py | 1 + avatar/migrations/0001_initial.py | 1 - ...0002_add_verbose_names_to_avatar_fields.py | 1 - avatar/migrations/0003_auto_20170827_1345.py | 1 - avatar/providers.py | 27 ++++++++++ docs/index.txt | 1 + pyproject.toml | 50 +++++++++++++++++++ requirements.txt | 3 ++ setup.py | 1 + tests/requirements.txt | 2 +- 12 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 pyproject.toml create mode 100644 requirements.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34ba072..143caa1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,29 +5,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + python-version: ['3.8', '3.9', '3.10', '3.11'] + django-version: ['3.2', '4.0', '4.1'] include: - - python-version: 3.6 - django-version: 3.2 - python-version: 3.7 django-version: 3.2 - - python-version: 3.8 - django-version: 3.2 - - python-version: 3.9 - django-version: 3.2 - - python-version: '3.10' - django-version: 3.2 - - python-version: 3.8 - django-version: 4.0 - - python-version: 3.9 - django-version: 4.0 - - python-version: '3.10' - django-version: 4.0 - - python-version: 3.8 - django-version: 4.1 - - python-version: 3.9 - django-version: 4.1 - - python-version: '3.10' - django-version: 4.1 + fail-fast: false + steps: - uses: actions/checkout@v3 - name: 'Set up Python ${{ matrix.python-version }}' @@ -37,7 +21,7 @@ jobs: cache: 'pip' - name: Install dependencies run: | - pip install -e . + pip install -r requirements.txt pip install -r tests/requirements.txt pip install "Django~=${{ matrix.django-version }}.0" . - name: Run Tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4923f8a..adb8393 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,24 +1,24 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/isort - rev: "5.10.1" + rev: "5.12.0" hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 23.1.0 hooks: - id: black args: [--target-version=py310] - repo: https://github.com/pycqa/flake8 - rev: '5.0.4' + rev: '6.0.0' hooks: - id: flake8 additional_dependencies: diff --git a/avatar/conf.py b/avatar/conf.py index 1a66e21..e3a49fe 100644 --- a/avatar/conf.py +++ b/avatar/conf.py @@ -35,6 +35,7 @@ class AvatarConf(AppConf): DELETE_TEMPLATE = "" PROVIDERS = ( "avatar.providers.PrimaryAvatarProvider", + "avatar.providers.LibRAvatarProvider", "avatar.providers.GravatarAvatarProvider", "avatar.providers.DefaultAvatarProvider", ) diff --git a/avatar/migrations/0001_initial.py b/avatar/migrations/0001_initial.py index 3ef74bc..0afa7bc 100644 --- a/avatar/migrations/0001_initial.py +++ b/avatar/migrations/0001_initial.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] 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 52700e1..20f343e 100644 --- a/avatar/migrations/0002_add_verbose_names_to_avatar_fields.py +++ b/avatar/migrations/0002_add_verbose_names_to_avatar_fields.py @@ -8,7 +8,6 @@ class Migration(migrations.Migration): - dependencies = [ ("avatar", "0001_initial"), ] diff --git a/avatar/migrations/0003_auto_20170827_1345.py b/avatar/migrations/0003_auto_20170827_1345.py index 8e39423..849cb8c 100644 --- a/avatar/migrations/0003_auto_20170827_1345.py +++ b/avatar/migrations/0003_auto_20170827_1345.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("avatar", "0002_add_verbose_names_to_avatar_fields"), ] diff --git a/avatar/providers.py b/avatar/providers.py index 8bb996e..b55014b 100644 --- a/avatar/providers.py +++ b/avatar/providers.py @@ -1,6 +1,8 @@ import hashlib +import re from urllib.parse import urlencode, urljoin +import dns.resolver from django.utils.module_loading import import_string from avatar.conf import settings @@ -64,6 +66,31 @@ def get_avatar_url(cls, user, width, _height=None): return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path) +class LibRAvatarProvider: + """ + Returns the url of an avatar by the Ravatar service. + """ + + @classmethod + def get_avatar_url(cls, user, width, _height=None): + email = getattr(user, settings.AVATAR_GRAVATAR_FIELD).encode("utf-8") + _, domain = email.split(b"@") + try: + answers = dns.resolver.query("_avatars._tcp." + domain, "SRV") + hostname = re.sub(r"\.$", "", str(answers[0].target)) + # query returns "example.com." and while http requests are fine with this, + # https most certainly do not consider "example.com." and "example.com" to be the same. + port = str(answers[0].port) + if port == "443": + baseurl = "https://" + hostname + "/avatar/" + else: + baseurl = "http://" + hostname + ":" + port + "/avatar/" + except Exception: + baseurl = "https://seccdn.libravatar.org/avatar/" + hash = hashlib.md5(email.strip().lower()).hexdigest() + return baseurl + hash + + class FacebookAvatarProvider(object): """ Returns the url of a Facebook profile image. diff --git a/docs/index.txt b/docs/index.txt index 922056c..41ae81a 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -180,6 +180,7 @@ appear on the site. Listed below are those settings: ( 'avatar.providers.PrimaryAvatarProvider', + 'avatar.providers.LibRAvatarProvider', 'avatar.providers.GravatarAvatarProvider', 'avatar.providers.DefaultAvatarProvider', ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..499f48d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[tool.poetry] +name = "django-avatar" +version = "7.0.1" +description = "A Django app for handling user avatars" +authors = ["Eric Florenzano "] +maintainers = ["Johannes Wilm "] +license = "BSD-4-Clause" +readme = "README.rst" +packages = [ + { include = "avatar" }, +] +exclude = [ + { path = "tests"}, +] +homepage = "https://github.com/jazzband/django-avatar" +repository = "https://github.com/jazzband/django-avatar" +documentation = "https://django-avatar.readthedocs.io" +keywords=["avatar", "django"] +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Intended Audience :: Developers", + "Framework :: Django", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.0", + "Framework :: Django :: 4.1", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + ] + +[tool.poetry.dependencies] +python = "^3.7" +Pillow = "^9.4.0" +django-appconf = "^1.0.5" +dnspython = "^2.3.0" + +[tool.poetry.group.dev.dependencies] +coverage = "^7.1.0" +python-magic = "^0.4.27" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ff2aef2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Pillow>=8.4.0 +django-appconf>=1.0.5 +dnspython>=2.3.0 diff --git a/setup.py b/setup.py index f58440b..d63cfff 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ def find_version(*file_paths): install_requires=[ "Pillow>=8.4.0", "django-appconf>=1.0.5", + "dnspython>=2.3.0", ], zip_safe=False, ) diff --git a/tests/requirements.txt b/tests/requirements.txt index 04d12f0..c326a12 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,3 @@ -coverage==6.2 +coverage==7.1.0 django python-magic