Add an Avatar Service Registry to Djblets
Review Request #7844 — Created Jan. 6, 2016 and submitted
The avatar services registry is a new object for managing avatar
services, which are defined to provide multiple ways of displaying
users' avatars. Currently, the only supported service is the Gravatar
(https://gravatar.com) service.Avatars support the
srcset
attribute for<img>
tags. In browsers
where it is not supported, a polyfill is used.
- Ran unit tests
- Used this with an upcoming patch in Review Board.
- Tested the
srcset
polyfill in IE11 with Review Board.
Description | From | Last Updated |
---|---|---|
Col: 1 W391 blank line at end of file |
reviewbot | |
Col: 21 E128 continuation line under-indented for visual indent |
reviewbot | |
Col: 21 E128 continuation line under-indented for visual indent |
reviewbot | |
Col: 21 E128 continuation line under-indented for visual indent |
reviewbot | |
Col: 1 E303 too many blank lines (3) |
reviewbot | |
The suspense is killing me. |
david | |
Blank line after this. |
david | |
Blank line after this. |
david | |
Missing the "Yields" label. |
david | |
Missing "Raises" |
david | |
Should we include 3x for future proofing? |
david | |
This is wordy. Can we drop the first "services"? Maybe just "avatars_enabled_services" and "avatars_default_service"? |
chipx86 | |
The set shouldn't be there. |
chipx86 | |
Given the complexity of this and the getter (more this one), and the note about having to call save manually, … |
chipx86 | |
I feel that we should be using subclasses of this. Much of the implementation of registries should be an internal … |
chipx86 | |
Needs the full path. Same with others below. |
chipx86 | |
There's an extra "and the". |
chipx86 | |
Instead of passing a default, we should fall back on what the app may have specified as the default when … |
chipx86 | |
No blank line. |
chipx86 | |
If we make use of siteconfig defaults, this information can be encoded there, rather than being hard-coded here. |
chipx86 | |
"human-readable" |
chipx86 | |
This sounds like the template will be rendering an image, but it's the HTML tags for an image. |
chipx86 | |
Extra "the" |
chipx86 | |
Alignment issue. |
chipx86 | |
A more efficient way of doing this would be: try: urls = request._avatar_cache[key] except KeyError: urls = self.get_blah(...) request._avatar_cache[key] = … |
chipx86 | |
Alignment issue. |
chipx86 | |
Let's change this to: raise NotImplementedError( '%r must implement get_avatar_urls_uncached().' % self.__class__ ) That way, when there are lots spewing … |
chipx86 | |
80 is the default for gravatars, but we care more about the caller. The caller should expect consistency in sizes, … |
chipx86 | |
We could support all ##x image sizes by doing: <img {% for key, url in urls.items %}{% if key|startswith:"@" %}{% … |
chipx86 | |
We should probably raise a ValueError if fmt is empty, instead of asserting, and specify the class and error name, … |
chipx86 | |
'mark_safe' imported but unused |
reviewbot | |
Description on the next line. |
chipx86 | |
The else is implied, so you can remove it. The first part is a guard, and doesn't have to be … |
chipx86 | |
You can remove the blank line here. |
chipx86 | |
This is already saved below. Also, are there cases where we don't need to save? |
chipx86 | |
Thinking this should be avatar_id. Some of our older stuff uses id (which is a reserved word), and most of … |
chipx86 | |
This is going to be a SafeText. |
chipx86 | |
Blank line not needed. |
chipx86 | |
Having a hard time parsing this. Maybe "The keyword arguments passed to the error-specific formatting string." |
chipx86 | |
No need for None. |
chipx86 | |
Let's include the class name in this (using __class__). |
chipx86 | |
No blank line. |
chipx86 | |
We shouldn't assume anything about the safeness of the URLs. If the URL has a & in it, then it'll … |
chipx86 |
-
Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst
-
-
Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst
- Change Summary:
-
Addressed David's issues.
- Commit:
-
5146fb6b4e9a1d9b85c9bbf09facc8a57a388aeb
- Diff:
-
Revision 4 (+1001 -12)
-
Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst
-
-
This is wordy. Can we drop the first "services"? Maybe just "avatars_enabled_services" and "avatars_default_service"?
-
-
Given the complexity of this and the getter (more this one), and the note about having to call save manually, and how we have similar functions that have save as a built-in flag, I think maybe we should just use standard methods for this.
Properties are great when it's a trivial thing, like taking a value and stuffing it in some nested object, or normalizing the value. A caller expects that an assignment like that will basically just work. This one is a lot more complicated, with the possibility of other exceptions being raised and the other requirements placed on the caller.
-
I feel that we should be using subclasses of this. Much of the implementation of registries should be an internal detail, and not something that the caller should have to know much about. Right now, as a caller, I have to learn not just about the avatar service, but I also have to dive into the registry methods.
What about extending registries to have a class pointing to the exceptions that should be used? Then there could be an
AvatarNotFoundError
that's raised, subclassingItemLookupError
, when raised either by this subclass or by the base class.Such an error would have a lot more meaning to a user, and to anyone reading or contributing to the code.
-
-
-
Instead of passing a default, we should fall back on what the app may have specified as the default when registering settings. That way, we get that value even if not in the database.
-
-
If we make use of siteconfig defaults, this information can be encoded there, rather than being hard-coded here.
-
-
-
-
-
A more efficient way of doing this would be:
try: urls = request._avatar_cache[key] except KeyError: urls = self.get_blah(...) request._avatar_cache[key] = urls
-
-
Let's change this to:
raise NotImplementedError( '%r must implement get_avatar_urls_uncached().' % self.__class__ )
That way, when there are lots spewing errors, it's very clear what class is missing this definition.
(Also parens to be clear it's a method.)
-
80 is the default for gravatars, but we care more about the caller. The caller should expect consistency in sizes, regardless of what the defaults are in the backend if a size is unspecified.
Realistically, this value will almost surely never be used, because
get_avatar_urls()
is going to pass its own value.I'd suggest having
get_avatar_urls_uncached
, both here and inAvatarService
, simply not take a default value forsize
. -
We could support all
##x
image sizes by doing:<img {% for key, url in urls.items %}{% if key|startswith:"@" %}{% if key == "@1x" %}src{% else %}{{key}}{% endif %}="{{url}}"{% endif %} />
However,
<img srcset="..."/>
and<picture/>
are becoming things. These are neat because not only do they let you do "2x", "3x", etc., but also "512w" (for a 512 image width), which we could then allow in the list of URLs.I think we could probably start trying to use this now (would need some preliminary testing), whether this change or another, and bonus points: We could just find a polyfill for it instead of writing and requiring use of
$.retinaAvatar
.This could look like:
<img src="{{urls.1x}}" srcset="{% for size, url in urls.items %}{% spaceless %} {{url}}{% if size != "1x" %} {{size}}{% endif %}{% if not forloop.last %}, {% endif %} {% endspaceless %}{% endfor %}" />
(I'm just leaving off the other attributes from these examples, but we'd obviously still want them.)
-
We should probably raise a
ValueError
iffmt
is empty, instead of asserting, and specify the class and error name, so it's more clear what's going on if this is hit.
- Change Summary:
-
Addressed Christian's issues.
- Description:
-
The avatar services registry is a new object for managing avatar
services, which are defined to provide multiple ways of displaying users' avatars. Currently, the only supported service is the Gravatar (https://gravatar.com) service. + Avatars support the
srcset
attribute for<img>
tags. In browsers+ where it is not supported, a polyfill is used. + TODO: Docs still need to be added for this, but I want to get the API
stabilized first before I write the docs. - Testing Done:
-
- Ran unit tests
- Used this with an upcoming patch in Review Board.
+ - Tested the
srcset
polyfill in IE11 with Review Board.
- Diff:
-
Revision 5 (+1497 -23)
-
Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/configforms/mixins.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/configforms/registry.py djblets/avatars/tests.py djblets/avatars/registry.py djblets/configforms/tests.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/configforms/mixins.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/configforms/registry.py djblets/avatars/tests.py djblets/avatars/registry.py djblets/configforms/tests.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst
-
This looks good to me now. May want to get Christian to give it another look too, since it's pretty big.
- Change Summary:
-
Remove the configforms stuff from this patch.
It was uploaded by accident. - Diff:
-
Revision 6 (+1093 -23)
-
Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/services/__init__.py djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js djblets/avatars/__init__.py docs/djblets/coderef/index.rst
-
Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js docs/djblets/guides/index.rst djblets/avatars/services/__init__.py docs/djblets/guides/avatars/writing-avatar-services.rst djblets/avatars/__init__.py docs/djblets/coderef/index.rst docs/djblets/guides/avatars/index.rst docs/djblets/guides/registries/writing-registries.rst Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js docs/djblets/guides/index.rst djblets/avatars/services/__init__.py docs/djblets/guides/avatars/writing-avatar-services.rst djblets/avatars/__init__.py docs/djblets/coderef/index.rst docs/djblets/guides/avatars/index.rst docs/djblets/guides/registries/writing-registries.rst
-
-
Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js docs/djblets/guides/index.rst djblets/avatars/services/__init__.py docs/djblets/guides/avatars/writing-avatar-services.rst djblets/avatars/__init__.py docs/djblets/coderef/index.rst docs/djblets/guides/avatars/index.rst docs/djblets/guides/registries/writing-registries.rst Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js docs/djblets/guides/index.rst djblets/avatars/services/__init__.py docs/djblets/guides/avatars/writing-avatar-services.rst djblets/avatars/__init__.py docs/djblets/coderef/index.rst docs/djblets/guides/avatars/index.rst docs/djblets/guides/registries/writing-registries.rst
- Description:
-
The avatar services registry is a new object for managing avatar
services, which are defined to provide multiple ways of displaying users' avatars. Currently, the only supported service is the Gravatar (https://gravatar.com) service. Avatars support the
srcset
attribute for<img>
tags. In browserswhere it is not supported, a polyfill is used. - - TODO: Docs still need to be added for this, but I want to get the API
- stabilized first before I write the docs.
-
Huh, was sure I published this...
-
-
The
else
is implied, so you can remove it. The first part is a guard, and doesn't have to be connected to the rest.Same with ones below.
-
-
-
Thinking this should be
avatar_id
. Some of our older stuff usesid
(which is a reserved word), and most of our newer stuff uses things likeavatar_id
. -
-
-
Having a hard time parsing this. Maybe "The keyword arguments passed to the error-specific formatting string."
-
-
-
-
We shouldn't assume anything about the safeness of the URLs. If the URL has a
&
in it, then it'll turn into an entity here. So instead, let's do:return format_html_join( ', ' '{0} {1}', ( (url, descriptor) for descriptor, url in ... if url is ... ))
-
Tool: PEP8 Style Checker Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js docs/djblets/guides/index.rst djblets/avatars/services/__init__.py docs/djblets/guides/avatars/writing-avatar-services.rst djblets/avatars/__init__.py docs/djblets/coderef/index.rst docs/djblets/guides/avatars/index.rst docs/djblets/guides/registries/writing-registries.rst Tool: Pyflakes Processed Files: djblets/registries/registry.py djblets/util/templatetags/djblets_images.py djblets/avatars/services/gravatar.py djblets/avatars/services/base.py djblets/avatars/errors.py djblets/avatars/tests.py djblets/avatars/registry.py Ignored Files: djblets/avatars/templates/avatars/avatar.html djblets/static/djblets/js/jquery.gravy.retina.js docs/djblets/guides/index.rst djblets/avatars/services/__init__.py docs/djblets/guides/avatars/writing-avatar-services.rst djblets/avatars/__init__.py docs/djblets/coderef/index.rst docs/djblets/guides/avatars/index.rst docs/djblets/guides/registries/writing-registries.rst