mirror of
https://github.com/jazzband/django-avatar.git
synced 2026-03-16 22:20:30 +00:00
Avatar API Added (#232)
* Add Avatar API support * extend rest_framework to INSTALLED_APPS * add api path to urls * add requirements.txt to api app * add self describe to assign_width_or_height function * add django-avatar api docs * remove unused files * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Johannes Wilm <mail@johanneswilm.org>
This commit is contained in:
parent
9d43467934
commit
81ace968c7
18 changed files with 1014 additions and 4 deletions
1
avatar/api/__init__.py
Normal file
1
avatar/api/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
import avatar.api.signals
|
||||||
6
avatar/api/apps.py
Normal file
6
avatar/api/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "avatar.api"
|
||||||
7
avatar/api/conf.py
Normal file
7
avatar/api/conf.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from appconf import AppConf
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarAPIConf(AppConf):
|
||||||
|
# allow updating avatar image in put method
|
||||||
|
AVATAR_CHANGE_IMAGE = False
|
||||||
20
avatar/api/docs/Makefile
Normal file
20
avatar/api/docs/Makefile
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line, and also
|
||||||
|
# from the environment for the first two.
|
||||||
|
SPHINXOPTS ?=
|
||||||
|
SPHINXBUILD ?= sphinx-build
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
191
avatar/api/docs/avatar.rst
Normal file
191
avatar/api/docs/avatar.rst
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
API Descriptions
|
||||||
|
================
|
||||||
|
|
||||||
|
Avatar List
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
||||||
|
send a request for listing user avatars as shown below.
|
||||||
|
|
||||||
|
``GET`` ``/api/avatar/``
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
default response of avatar list : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"message": "You haven't uploaded an avatar yet. Please upload one now.",
|
||||||
|
"default_avatar": {
|
||||||
|
"src": "https://seccdn.libravatar.org/avatar/4a9328d595472d0728195a7c8191a50b",
|
||||||
|
"width": "80",
|
||||||
|
"height": "80",
|
||||||
|
"alt": "User Avatar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if you have an avatar object : ::
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "image_id",
|
||||||
|
"avatar_url": "https://example.com/api/avatar/1/",
|
||||||
|
"avatar": "https://example.com/media/avatars/1/first_avatar.png",
|
||||||
|
"primary": true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Create Avatar
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
||||||
|
send a request for creating user avatar as shown below .
|
||||||
|
|
||||||
|
``POST`` ``/api/avatar/``
|
||||||
|
|
||||||
|
|
||||||
|
Request : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"avatar": "image file",
|
||||||
|
"primary": true
|
||||||
|
}
|
||||||
|
|
||||||
|
``Note`` : avatar field is required.
|
||||||
|
|
||||||
|
Response : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"message": "Successfully uploaded a new avatar.",
|
||||||
|
"data": {
|
||||||
|
"id": "image_id",
|
||||||
|
"avatar_url": "https://example.com/api/avatar/1/",
|
||||||
|
"avatar": "https://example.com/media/avatars/1/example.png",
|
||||||
|
"primary": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Avatar Detail
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
||||||
|
send a request for retrieving user avatar.
|
||||||
|
|
||||||
|
``GET`` ``/api/avatar/image_id/``
|
||||||
|
|
||||||
|
|
||||||
|
Response : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "image_id",
|
||||||
|
"avatar": "https://example.com/media/avatars/1/example.png",
|
||||||
|
"primary": true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Update Avatar
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
||||||
|
send a request for updating user avatar.
|
||||||
|
|
||||||
|
``PUT`` ``/api/avatar/image_id/``
|
||||||
|
|
||||||
|
|
||||||
|
Request : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"avatar":"image file"
|
||||||
|
"primary": true
|
||||||
|
}
|
||||||
|
|
||||||
|
``Note`` : for update avatar image set ``API_AVATAR_CHANGE_IMAGE = True`` in your settings file and set ``primary = True``.
|
||||||
|
|
||||||
|
Response : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"message": "Successfully updated your avatar.",
|
||||||
|
"data": {
|
||||||
|
"id": "image_id",
|
||||||
|
"avatar": "https://example.com/media/avatars/1/custom_admin_en.png",
|
||||||
|
"primary": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Delete Avatar
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
||||||
|
send a request for deleting user avatar.
|
||||||
|
|
||||||
|
``DELETE`` ``/api/avatar/image_id/``
|
||||||
|
|
||||||
|
|
||||||
|
Response : ::
|
||||||
|
|
||||||
|
"Successfully deleted the requested avatars."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Render Primary Avatar
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
send a request for retrieving resized primary avatar .
|
||||||
|
|
||||||
|
|
||||||
|
default sizes ``80``:
|
||||||
|
|
||||||
|
``GET`` ``/api/avatar/render_primary/``
|
||||||
|
|
||||||
|
Response : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"image_url": "https://example.com/media/avatars/1/resized/80/80/example.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
custom ``width`` and ``height`` :
|
||||||
|
|
||||||
|
``GET`` ``/api/avatar/render_primary/?width=width_size&height=height_size``
|
||||||
|
|
||||||
|
Response : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"image_url": "http://127.0.0.1:8000/media/avatars/1/resized/width_size/height_size/python.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
If the entered parameter is one of ``width`` or ``height``, it will be considered for both .
|
||||||
|
|
||||||
|
``GET`` ``/api/avatar/render_primary/?width=size`` :
|
||||||
|
|
||||||
|
Response : ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"image_url": "http://127.0.0.1:8000/media/avatars/1/resized/size/size/python.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
``Note`` : Resize parameters not working for default avatar.
|
||||||
|
|
||||||
|
API Setting
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. py:data:: API_AVATAR_CHANGE_IMAGE
|
||||||
|
|
||||||
|
It Allows the user to Change the avatar image in ``PUT`` method. Default is ``False``.
|
||||||
33
avatar/api/docs/conf.py
Normal file
33
avatar/api/docs/conf.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# For the full list of built-in configuration values, see the documentation:
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
|
|
||||||
|
project = "django-avatar"
|
||||||
|
copyright = "2013, django-avatar developers"
|
||||||
|
author = "keyvan"
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|
||||||
|
extensions = []
|
||||||
|
|
||||||
|
templates_path = ["_templates"]
|
||||||
|
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||||
|
|
||||||
|
htmlhelp_basename = "django-avatar apidoc"
|
||||||
|
|
||||||
|
source_suffix = ".rst"
|
||||||
|
|
||||||
|
master_doc = "index"
|
||||||
|
pygments_style = "sphinx"
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
html_static_path = ["_static"]
|
||||||
|
epub_show_urls = "footnote"
|
||||||
310
avatar/api/docs/index.rst
Normal file
310
avatar/api/docs/index.rst
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
|
||||||
|
django-avatar
|
||||||
|
=============
|
||||||
|
|
||||||
|
Django-avatar is a reusable application for handling user avatars. It has the
|
||||||
|
ability to default to avatars provided by third party services (like Gravatar_
|
||||||
|
or Facebook) if no avatar is found for a certain user. Django-avatar
|
||||||
|
automatically generates thumbnails and stores them to your default file
|
||||||
|
storage backend for retrieval later.
|
||||||
|
|
||||||
|
.. _Gravatar: https://gravatar.com
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you have pip_ installed, you can simply run the following command
|
||||||
|
to install django-avatar::
|
||||||
|
|
||||||
|
pip install django-avatar
|
||||||
|
|
||||||
|
Included with this application is a file named ``setup.py``. It's possible to
|
||||||
|
use this file to install this application to your system, by invoking the
|
||||||
|
following command::
|
||||||
|
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
|
Once that's done, you should be able to begin using django-avatar at will.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
To integrate ``django-avatar`` with your site, there are relatively few things
|
||||||
|
that are required. A minimal integration can work like this:
|
||||||
|
|
||||||
|
1. List this application in the ``INSTALLED_APPS`` portion of your settings
|
||||||
|
file. Your settings file will look something like::
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
# ...
|
||||||
|
'avatar',
|
||||||
|
)
|
||||||
|
|
||||||
|
2. Migrate your database::
|
||||||
|
|
||||||
|
python manage.py migrate
|
||||||
|
|
||||||
|
3. Add the avatar urls to the end of your root urlconf. Your urlconf
|
||||||
|
will look something like::
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# ...
|
||||||
|
path('avatar/', include('avatar.urls')),
|
||||||
|
]
|
||||||
|
|
||||||
|
4. Somewhere in your template navigation scheme, link to the change avatar
|
||||||
|
page::
|
||||||
|
|
||||||
|
<a href="{% url 'avatar_change' %}">Change your avatar</a>
|
||||||
|
|
||||||
|
5. Wherever you want to display an avatar for a user, first load the avatar
|
||||||
|
template tags::
|
||||||
|
|
||||||
|
{% load avatar_tags %}
|
||||||
|
|
||||||
|
Then, use the ``avatar`` tag to display an avatar of a default size::
|
||||||
|
|
||||||
|
{% avatar user %}
|
||||||
|
|
||||||
|
Or specify a size (in pixels) explicitly::
|
||||||
|
|
||||||
|
{% avatar user 65 %}
|
||||||
|
|
||||||
|
Or specify a width and height (in pixels) explicitly::
|
||||||
|
|
||||||
|
{% avatar user 65 50 %}
|
||||||
|
|
||||||
|
Example for customize the attribute of the HTML ``img`` tag::
|
||||||
|
|
||||||
|
{% avatar user 65 class="img-circle img-responsive" id="user_avatar" %}
|
||||||
|
|
||||||
|
Template tags and filter
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
To begin using these template tags, you must first load the tags into the
|
||||||
|
template rendering system::
|
||||||
|
|
||||||
|
{% load avatar_tags %}
|
||||||
|
|
||||||
|
``{% avatar_url user [size in pixels] %}``
|
||||||
|
Renders the URL of the avatar for the given user. User can be either a
|
||||||
|
``django.contrib.auth.models.User`` object instance or a username.
|
||||||
|
|
||||||
|
``{% avatar user [size in pixels] **kwargs %}``
|
||||||
|
Renders an HTML ``img`` tag for the given user for the specified size. User
|
||||||
|
can be either a ``django.contrib.auth.models.User`` object instance or a
|
||||||
|
username. The (key, value) pairs in kwargs will be added to ``img`` tag
|
||||||
|
as its attributes.
|
||||||
|
|
||||||
|
``{% render_avatar avatar [size in pixels] %}``
|
||||||
|
Given an actual ``avatar.models.Avatar`` object instance, renders an HTML
|
||||||
|
``img`` tag to represent that avatar at the requested size.
|
||||||
|
|
||||||
|
``{{ request.user|has_avatar }}``
|
||||||
|
Given a user object returns a boolean if the user has an avatar.
|
||||||
|
|
||||||
|
Global Settings
|
||||||
|
---------------
|
||||||
|
|
||||||
|
There are a number of settings available to easily customize the avatars that
|
||||||
|
appear on the site. Listed below are those settings:
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_AUTO_GENERATE_SIZES
|
||||||
|
|
||||||
|
An iterable of integers and/or sequences in the format ``(width, height)``
|
||||||
|
representing the sizes of avatars to generate on upload. This can save
|
||||||
|
rendering time later on if you pre-generate the resized versions. Defaults
|
||||||
|
to ``(80,)``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_CACHE_ENABLED
|
||||||
|
|
||||||
|
Set to ``False`` if you completely disable avatar caching. Defaults to ``True``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_DEFAULT_URL
|
||||||
|
|
||||||
|
The default URL to default to if the
|
||||||
|
:py:class:`~avatar.providers.GravatarAvatarProvider` is not used and there
|
||||||
|
is no ``Avatar`` instance found in the system for the given user.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_EXPOSE_USERNAMES
|
||||||
|
|
||||||
|
Puts the User's username field in the URL path when ``True``. Set to
|
||||||
|
``False`` to use the User's primary key instead, preventing their email
|
||||||
|
from being searchable on the web. Defaults to ``False``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_FACEBOOK_GET_ID
|
||||||
|
|
||||||
|
A callable or string path to a callable that will return the user's
|
||||||
|
Facebook ID. The callable should take a ``User`` object and return a
|
||||||
|
string. If you want to use this then make sure you included
|
||||||
|
:py:class:`~avatar.providers.FacebookAvatarProvider` in :py:data:`AVATAR_PROVIDERS`.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_GRAVATAR_DEFAULT
|
||||||
|
|
||||||
|
A string determining the default Gravatar. Can be a URL to a custom image
|
||||||
|
or a style of Gravatar. Ex. `retro`. All Available options listed in the
|
||||||
|
`Gravatar documentation <https://en.gravatar.com/site/implement/images
|
||||||
|
/#default-image>`_. Defaults to ``None``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_GRAVATAR_FORCEDEFAULT
|
||||||
|
|
||||||
|
A bool indicating whether or not to always use the default Gravitar. More
|
||||||
|
details can be found in the `Gravatar documentation
|
||||||
|
<https://en.gravatar.com/site/implement/images/#force-default>`_. Defaults
|
||||||
|
to ``False``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_GRAVATAR_FIELD
|
||||||
|
|
||||||
|
The name of the user's field containing the gravatar email. For example,
|
||||||
|
if you set this to ``gravatar`` then django-avatar will get the user's
|
||||||
|
gravatar in ``user.gravatar``. Defaults to ``email``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_MAX_SIZE
|
||||||
|
|
||||||
|
File size limit for avatar upload. Default is ``1024 * 1024`` (1 MB).
|
||||||
|
gravatar in ``user.gravatar``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_MAX_AVATARS_PER_USER
|
||||||
|
|
||||||
|
The maximum number of avatars each user can have. Default is ``42``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_PATH_HANDLER
|
||||||
|
|
||||||
|
Path to a method for avatar file path handling. Default is
|
||||||
|
``avatar.models.avatar_path_handler``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_PROVIDERS
|
||||||
|
|
||||||
|
Tuple of classes that are tried in the given order for returning avatar
|
||||||
|
URLs.
|
||||||
|
Defaults to::
|
||||||
|
|
||||||
|
(
|
||||||
|
'avatar.providers.PrimaryAvatarProvider',
|
||||||
|
'avatar.providers.LibRAvatarProvider',
|
||||||
|
'avatar.providers.GravatarAvatarProvider',
|
||||||
|
'avatar.providers.DefaultAvatarProvider',
|
||||||
|
)
|
||||||
|
|
||||||
|
If you want to implement your own provider, it must provide a class method
|
||||||
|
``get_avatar_url(user, width, height)``.
|
||||||
|
|
||||||
|
.. py:class:: avatar.providers.PrimaryAvatarProvider
|
||||||
|
|
||||||
|
Returns the primary avatar stored for the given user.
|
||||||
|
|
||||||
|
.. py:class:: avatar.providers.GravatarAvatarProvider
|
||||||
|
|
||||||
|
Adds support for the Gravatar service and will always return an avatar
|
||||||
|
URL. If the user has no avatar registered with Gravatar a default will
|
||||||
|
be used (see :py:data:`AVATAR_GRAVATAR_DEFAULT`).
|
||||||
|
|
||||||
|
.. py:class:: avatar.providers.FacebookAvatarProvider
|
||||||
|
|
||||||
|
Add this provider to :py:data:`AVATAR_PROVIDERS` in order to add
|
||||||
|
support for profile images from Facebook. Note that you also need to
|
||||||
|
set the :py:data:`AVATAR_FACEBOOK_GET_ID` setting.
|
||||||
|
|
||||||
|
.. py:class:: avatar.providers.DefaultAvatarProvider
|
||||||
|
|
||||||
|
Provides a fallback avatar defined in :py:data:`AVATAR_DEFAULT_URL`.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_RESIZE_METHOD
|
||||||
|
|
||||||
|
The method to use when resizing images, based on the options available in
|
||||||
|
Pillow. Defaults to ``Image.LANCZOS``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_STORAGE_DIR
|
||||||
|
|
||||||
|
The directory under ``MEDIA_ROOT`` to store the images. If using a
|
||||||
|
non-filesystem storage device, this will simply be appended to the beginning
|
||||||
|
of the file name. Defaults to ``avatars``.
|
||||||
|
Pillow. Defaults to ``Image.ANTIALIAS``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_THUMB_FORMAT
|
||||||
|
|
||||||
|
The file format of thumbnails, based on the options available in
|
||||||
|
Pillow. Defaults to `PNG`.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_THUMB_QUALITY
|
||||||
|
|
||||||
|
The quality of thumbnails, between 0 (worst) to 95 (best) or the string
|
||||||
|
"keep" (only JPEG) as provided by Pillow. Defaults to `85`.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_THUMB_MODES
|
||||||
|
|
||||||
|
A sequence of acceptable modes for thumbnails as provided by Pillow. If the mode
|
||||||
|
of the image is not in the list, the thumbnail will be converted to the
|
||||||
|
first mode in the list. Defaults to `('RGB', 'RGBA')`.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_CLEANUP_DELETED
|
||||||
|
|
||||||
|
``True`` if the avatar image files should be deleted when an avatar is
|
||||||
|
deleted from the database. Defaults to ``True``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_ADD_TEMPLATE
|
||||||
|
|
||||||
|
Path to the Django template to use for adding a new avatar. Defaults to
|
||||||
|
``avatar/add.html``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_CHANGE_TEMPLATE
|
||||||
|
|
||||||
|
Path to the Django template to use for changing a user's avatar. Defaults to ``avatar/change.html``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_DELETE_TEMPLATE
|
||||||
|
|
||||||
|
Path to the Django template to use for confirming a delete of a user's
|
||||||
|
avatar. Defaults to ``avatar/avatar/confirm_delete.html``.
|
||||||
|
|
||||||
|
.. py:data:: AVATAR_ALLOWED_MIMETYPES
|
||||||
|
|
||||||
|
Limit allowed avatar image uploads by their actual content payload and what image codecs we wish to support.
|
||||||
|
This limits website user content site attack vectors against image codec buffer overflow and similar bugs.
|
||||||
|
`You must have python-imaging library installed <https://github.com/ahupp/python-magic>`_.
|
||||||
|
Suggested safe setting: ``("image/png", "image/gif", "image/jpeg")``.
|
||||||
|
When enabled you'll get the following error on the form upload *File content is invalid. Detected: image/tiff Allowed content types are: image/png, image/gif, image/jpg*.
|
||||||
|
|
||||||
|
|
||||||
|
Management Commands
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
This application does include one management command: ``rebuild_avatars``. It
|
||||||
|
takes no arguments and, when run, re-renders all of the thumbnails for all of
|
||||||
|
the avatars for the pixel sizes specified in the
|
||||||
|
:py:data:`AVATAR_AUTO_GENERATE_SIZES` setting.
|
||||||
|
|
||||||
|
|
||||||
|
.. _pip: https://www.pip-installer.org/
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
API
|
||||||
|
---
|
||||||
|
|
||||||
|
To use API there are relatively few things that are required.
|
||||||
|
|
||||||
|
after `Installation <#installation>`_ .
|
||||||
|
|
||||||
|
1. in your ``INSTALLED_APPS`` of your settings file : ::
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
# ...
|
||||||
|
'avatar',
|
||||||
|
'rest_framework'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
2. Add the avatar api urls to the end of your root url config : ::
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# ...
|
||||||
|
path('api/', include('avatar.api.urls')),
|
||||||
|
]
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
avatar
|
||||||
35
avatar/api/docs/make.bat
Normal file
35
avatar/api/docs/make.bat
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
|
if "%SPHINXBUILD%" == "" (
|
||||||
|
set SPHINXBUILD=sphinx-build
|
||||||
|
)
|
||||||
|
set SOURCEDIR=.
|
||||||
|
set BUILDDIR=_build
|
||||||
|
|
||||||
|
%SPHINXBUILD% >NUL 2>NUL
|
||||||
|
if errorlevel 9009 (
|
||||||
|
echo.
|
||||||
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||||
|
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||||
|
echo.may add the Sphinx directory to PATH.
|
||||||
|
echo.
|
||||||
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
|
echo.https://www.sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:help
|
||||||
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
|
||||||
|
:end
|
||||||
|
popd
|
||||||
0
avatar/api/migrations/__init__.py
Normal file
0
avatar/api/migrations/__init__.py
Normal file
1
avatar/api/requirements.txt
Normal file
1
avatar/api/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
djangorestframework
|
||||||
126
avatar/api/serializers.py
Normal file
126
avatar/api/serializers.py
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.template.defaultfilters import filesizeformat
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from avatar.conf import settings
|
||||||
|
from avatar.conf import settings as api_setting
|
||||||
|
from avatar.models import Avatar
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarSerializer(serializers.ModelSerializer):
|
||||||
|
avatar_url = serializers.HyperlinkedIdentityField(
|
||||||
|
view_name="avatar-detail",
|
||||||
|
)
|
||||||
|
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Avatar
|
||||||
|
fields = ["id", "avatar_url", "avatar", "primary", "user"]
|
||||||
|
extra_kwargs = {"avatar": {"required": True}}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
request = kwargs.get("context").get("request", None)
|
||||||
|
|
||||||
|
self.user = request.user
|
||||||
|
|
||||||
|
def get_fields(self, *args, **kwargs):
|
||||||
|
fields = super(AvatarSerializer, self).get_fields(*args, **kwargs)
|
||||||
|
request = self.context.get("request", None)
|
||||||
|
|
||||||
|
# remove avatar url field in detail page
|
||||||
|
if bool(self.context.get("view").kwargs):
|
||||||
|
fields.pop("avatar_url")
|
||||||
|
|
||||||
|
# remove avatar field in put method
|
||||||
|
if request and getattr(request, "method", None) == "PUT":
|
||||||
|
# avatar updates only when primary=true and API_AVATAR_CHANGE_IMAGE = True
|
||||||
|
if (
|
||||||
|
not api_setting.API_AVATAR_CHANGE_IMAGE
|
||||||
|
or self.instance
|
||||||
|
and not self.instance.primary
|
||||||
|
):
|
||||||
|
fields.pop("avatar")
|
||||||
|
else:
|
||||||
|
fields.get("avatar", None).required = False
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def validate_avatar(self, value):
|
||||||
|
data = value
|
||||||
|
|
||||||
|
if settings.AVATAR_ALLOWED_MIMETYPES:
|
||||||
|
try:
|
||||||
|
import magic
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
"python-magic library must be installed in order to use uploaded file content limitation"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct 256 bytes needed for mime validation
|
||||||
|
magic_buffer = bytes()
|
||||||
|
for chunk in data.chunks():
|
||||||
|
magic_buffer += chunk
|
||||||
|
if len(magic_buffer) >= 256:
|
||||||
|
break
|
||||||
|
|
||||||
|
# https://github.com/ahupp/python-magic#usage
|
||||||
|
mime = magic.from_buffer(magic_buffer, mime=True)
|
||||||
|
if mime not in settings.AVATAR_ALLOWED_MIMETYPES:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
_(
|
||||||
|
"File content is invalid. Detected: %(mimetype)s Allowed content types are: %(valid_mime_list)s"
|
||||||
|
)
|
||||||
|
% {
|
||||||
|
"valid_mime_list": ", ".join(settings.AVATAR_ALLOWED_MIMETYPES),
|
||||||
|
"mimetype": mime,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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 serializers.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 serializers.ValidationError(
|
||||||
|
error
|
||||||
|
% {
|
||||||
|
"size": filesizeformat(data.size),
|
||||||
|
"max_valid_size": filesizeformat(settings.AVATAR_MAX_SIZE),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
image = Image.open(data)
|
||||||
|
ImageOps.exif_transpose(image)
|
||||||
|
except TypeError:
|
||||||
|
raise serializers.ValidationError(_("Corrupted image"))
|
||||||
|
|
||||||
|
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 serializers.ValidationError(
|
||||||
|
error
|
||||||
|
% {
|
||||||
|
"nb_avatars": count,
|
||||||
|
"nb_max_avatars": settings.AVATAR_MAX_AVATARS_PER_USER,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return data
|
||||||
27
avatar/api/shortcut.py
Normal file
27
avatar/api/shortcut.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from django.shortcuts import _get_queryset
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_or_none(klass, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Use get() to return an object, or return None if the object
|
||||||
|
does not exist.
|
||||||
|
|
||||||
|
klass may be a Model, Manager, or QuerySet object. All other passed
|
||||||
|
arguments and keyword arguments are used in the get() query.
|
||||||
|
|
||||||
|
Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
|
||||||
|
one object is found.
|
||||||
|
"""
|
||||||
|
queryset = _get_queryset(klass)
|
||||||
|
if not hasattr(queryset, "get"):
|
||||||
|
klass__name = (
|
||||||
|
klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
|
||||||
|
)
|
||||||
|
raise ValueError(
|
||||||
|
"First argument to get_object_or_404() must be a Model, Manager, "
|
||||||
|
"or QuerySet, not '%s'." % klass__name
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return queryset.get(*args, **kwargs)
|
||||||
|
except queryset.model.DoesNotExist:
|
||||||
|
return None
|
||||||
56
avatar/api/signals.py
Normal file
56
avatar/api/signals.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.db.models import signals
|
||||||
|
|
||||||
|
from avatar.api.conf import settings as api_settings
|
||||||
|
from avatar.api.shortcut import get_object_or_none
|
||||||
|
from avatar.conf import settings
|
||||||
|
from avatar.models import Avatar, invalidate_avatar_cache
|
||||||
|
|
||||||
|
|
||||||
|
def create_default_thumbnails(sender, instance, created=False, **kwargs):
|
||||||
|
invalidate_avatar_cache(sender, instance)
|
||||||
|
|
||||||
|
if not created:
|
||||||
|
for size in settings.AVATAR_AUTO_GENERATE_SIZES:
|
||||||
|
if isinstance(size, int):
|
||||||
|
if not instance.thumbnail_exists(size, size):
|
||||||
|
instance.create_thumbnail(size, size)
|
||||||
|
else:
|
||||||
|
# Size is specified with height and width.
|
||||||
|
if not instance.thumbnail_exists(size[0, size[0]]):
|
||||||
|
instance.create_thumbnail(size[0], size[1])
|
||||||
|
|
||||||
|
|
||||||
|
def remove_previous_avatar_images_when_update(
|
||||||
|
sender, instance=None, created=False, update_main_avatar=True, **kwargs
|
||||||
|
):
|
||||||
|
if not created:
|
||||||
|
old_instance = get_object_or_none(Avatar, pk=instance.pk)
|
||||||
|
if old_instance and not old_instance.avatar == instance.avatar:
|
||||||
|
base_filepath = old_instance.avatar.name
|
||||||
|
path, filename = os.path.split(base_filepath)
|
||||||
|
# iterate through resized avatars directories and delete resized avatars
|
||||||
|
resized_path = os.path.join(path, "resized")
|
||||||
|
try:
|
||||||
|
resized_widths, _ = old_instance.avatar.storage.listdir(resized_path)
|
||||||
|
for width in resized_widths:
|
||||||
|
resized_width_path = os.path.join(resized_path, width)
|
||||||
|
resized_heights, _ = old_instance.avatar.storage.listdir(
|
||||||
|
resized_width_path
|
||||||
|
)
|
||||||
|
for height in resized_heights:
|
||||||
|
if old_instance.thumbnail_exists(width, height):
|
||||||
|
old_instance.avatar.storage.delete(
|
||||||
|
old_instance.avatar_name(width, height)
|
||||||
|
)
|
||||||
|
if update_main_avatar:
|
||||||
|
if old_instance.avatar.storage.exists(old_instance.avatar.name):
|
||||||
|
old_instance.avatar.storage.delete(old_instance.avatar.name)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if api_settings.API_AVATAR_CHANGE_IMAGE:
|
||||||
|
signals.pre_save.connect(remove_previous_avatar_images_when_update, sender=Avatar)
|
||||||
|
signals.post_save.connect(create_default_thumbnails, sender=Avatar)
|
||||||
8
avatar/api/urls.py
Normal file
8
avatar/api/urls.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from rest_framework.routers import SimpleRouter
|
||||||
|
|
||||||
|
from avatar.api.views import AvatarViewSets
|
||||||
|
|
||||||
|
router = SimpleRouter()
|
||||||
|
router.register("avatar", AvatarViewSets)
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
52
avatar/api/utils.py
Normal file
52
avatar/api/utils.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
|
||||||
|
from avatar.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLTagParser(HTMLParser):
|
||||||
|
"""
|
||||||
|
URL parser for getting (url ,width ,height) from avatar templatetags
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, output=None):
|
||||||
|
HTMLParser.__init__(self)
|
||||||
|
if output is None:
|
||||||
|
self.output = {}
|
||||||
|
else:
|
||||||
|
self.output = output
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
self.output.update(dict(attrs))
|
||||||
|
|
||||||
|
|
||||||
|
def assign_width_or_height(query_params):
|
||||||
|
"""
|
||||||
|
Getting width and height in url parameters and specifying them
|
||||||
|
"""
|
||||||
|
avatar_default_size = settings.AVATAR_DEFAULT_SIZE
|
||||||
|
|
||||||
|
width = query_params.get("width", avatar_default_size)
|
||||||
|
height = query_params.get("height", avatar_default_size)
|
||||||
|
|
||||||
|
if width == "":
|
||||||
|
width = avatar_default_size
|
||||||
|
if height == "":
|
||||||
|
height = avatar_default_size
|
||||||
|
|
||||||
|
if height == avatar_default_size and height != "":
|
||||||
|
height = width
|
||||||
|
elif width == avatar_default_size and width != "":
|
||||||
|
width = height
|
||||||
|
|
||||||
|
width = int(width)
|
||||||
|
height = int(height)
|
||||||
|
|
||||||
|
context = {"width": width, "height": height}
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def set_new_primary(query_set, instance):
|
||||||
|
queryset = query_set.exclude(id=instance.id).first()
|
||||||
|
if queryset:
|
||||||
|
queryset.primary = True
|
||||||
|
queryset.save()
|
||||||
134
avatar/api/views.py
Normal file
134
avatar/api/views.py
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import permissions, status, viewsets
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from avatar.api.serializers import AvatarSerializer
|
||||||
|
from avatar.api.utils import HTMLTagParser, assign_width_or_height, set_new_primary
|
||||||
|
from avatar.models import Avatar
|
||||||
|
from avatar.templatetags.avatar_tags import avatar
|
||||||
|
from avatar.utils import get_default_avatar_url, get_primary_avatar, invalidate_cache
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarViewSets(viewsets.ModelViewSet):
|
||||||
|
serializer_class = AvatarSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
queryset = Avatar.objects.select_related("user").order_by(
|
||||||
|
"-primary", "-date_uploaded"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parse_html_to_json(self):
|
||||||
|
default_avatar = avatar(self.request.user)
|
||||||
|
html_parser = HTMLTagParser()
|
||||||
|
html_parser.feed(default_avatar)
|
||||||
|
return html_parser.output
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
assert self.queryset is not None, (
|
||||||
|
"'%s' should either include a `queryset` attribute, "
|
||||||
|
"or override the `get_queryset()` method." % self.__class__.__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
queryset = self.queryset
|
||||||
|
if isinstance(queryset, QuerySet):
|
||||||
|
# Ensure queryset is re-evaluated on each request.
|
||||||
|
queryset = queryset.filter(user=self.request.user)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
if queryset:
|
||||||
|
page = self.paginate_queryset(queryset)
|
||||||
|
if page is not None:
|
||||||
|
serializer = self.get_serializer(page, many=True)
|
||||||
|
return self.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
data = serializer.data
|
||||||
|
return Response(data)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"message": "You haven't uploaded an avatar yet. Please upload one now.",
|
||||||
|
"default_avatar": self.parse_html_to_json,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
self.perform_create(serializer)
|
||||||
|
headers = self.get_success_headers(serializer.data)
|
||||||
|
message = _("Successfully uploaded a new avatar.")
|
||||||
|
|
||||||
|
context_data = {"message": message, "data": serializer.data}
|
||||||
|
return Response(context_data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
if instance.primary is True:
|
||||||
|
# Find the next avatar, and set it as the new primary
|
||||||
|
set_new_primary(self.get_queryset(), instance)
|
||||||
|
self.perform_destroy(instance)
|
||||||
|
message = _("Successfully deleted the requested avatars.")
|
||||||
|
return Response(message, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
partial = kwargs.pop("partial", False)
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
avatar_image = serializer.validated_data.get("avatar")
|
||||||
|
primary_avatar = serializer.validated_data.get("primary")
|
||||||
|
if not primary_avatar and avatar_image:
|
||||||
|
raise ValidationError("You cant update an avatar image that is not primary")
|
||||||
|
|
||||||
|
if instance.primary is True:
|
||||||
|
# Find the next avatar, and set it as the new primary
|
||||||
|
set_new_primary(self.get_queryset(), instance)
|
||||||
|
|
||||||
|
self.perform_update(serializer)
|
||||||
|
invalidate_cache(request.user)
|
||||||
|
message = _("Successfully updated your avatar.")
|
||||||
|
if getattr(instance, "_prefetched_objects_cache", None):
|
||||||
|
# If 'prefetch_related' has been applied to a queryset, we need to
|
||||||
|
# forcibly invalidate the prefetch cache on the instance.
|
||||||
|
instance._prefetched_objects_cache = {}
|
||||||
|
context_data = {"message": message, "data": serializer.data}
|
||||||
|
return Response(context_data)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
["GET"], detail=False, url_path="render_primary", name="Render Primary Avatar"
|
||||||
|
)
|
||||||
|
def render_primary(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
URL Example :
|
||||||
|
|
||||||
|
1 - render_primary/
|
||||||
|
2 - render_primary/?width=400 or render_primary/?height=400
|
||||||
|
3 - render_primary/?width=500&height=400
|
||||||
|
"""
|
||||||
|
context_data = {}
|
||||||
|
avatar_size = assign_width_or_height(request.query_params)
|
||||||
|
|
||||||
|
width = avatar_size.get("width")
|
||||||
|
height = avatar_size.get("height")
|
||||||
|
|
||||||
|
primary_avatar = get_primary_avatar(request.user, width=width, height=height)
|
||||||
|
|
||||||
|
if primary_avatar and primary_avatar.primary:
|
||||||
|
url = primary_avatar.avatar_url(width, height)
|
||||||
|
|
||||||
|
else:
|
||||||
|
url = get_default_avatar_url()
|
||||||
|
if bool(request.query_params):
|
||||||
|
context_data.update(
|
||||||
|
{"message": "Resize parameters not working for default avatar"}
|
||||||
|
)
|
||||||
|
|
||||||
|
context_data.update({"image_url": request.build_absolute_uri(url)})
|
||||||
|
return Response(context_data)
|
||||||
|
|
@ -38,6 +38,7 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"avatar",
|
"avatar",
|
||||||
|
"rest_framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import re_path
|
||||||
from django.views.static import serve
|
from django.views.static import serve
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^admin/", admin.site.urls),
|
re_path(r"^admin/", admin.site.urls),
|
||||||
url(r"^avatar/", include("avatar.urls")),
|
re_path(r"^avatar/", include("avatar.urls")),
|
||||||
|
re_path(r"^api/", include("avatar.api.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# static files (images, css, javascript, etc.)
|
# static files (images, css, javascript, etc.)
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r"^media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT})
|
re_path(r"^media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT})
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue