• 
      

    Add an Avatar Service Registry to Djblets

    Review Request #7844 — Created Jan. 6, 2016 and submitted

    Information

    Djblets
    release-0.10.x
    5146fb6...

    Reviewers

    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

    reviewbotreviewbot

    Col: 21 E128 continuation line under-indented for visual indent

    reviewbotreviewbot

    Col: 21 E128 continuation line under-indented for visual indent

    reviewbotreviewbot

    Col: 21 E128 continuation line under-indented for visual indent

    reviewbotreviewbot

    Col: 1 E303 too many blank lines (3)

    reviewbotreviewbot

    The suspense is killing me.

    daviddavid

    Blank line after this.

    daviddavid

    Blank line after this.

    daviddavid

    Missing the "Yields" label.

    daviddavid

    Missing "Raises"

    daviddavid

    Should we include 3x for future proofing?

    daviddavid

    This is wordy. Can we drop the first "services"? Maybe just "avatars_enabled_services" and "avatars_default_service"?

    chipx86chipx86

    The set shouldn't be there.

    chipx86chipx86

    Given the complexity of this and the getter (more this one), and the note about having to call save manually, …

    chipx86chipx86

    I feel that we should be using subclasses of this. Much of the implementation of registries should be an internal …

    chipx86chipx86

    Needs the full path. Same with others below.

    chipx86chipx86

    There's an extra "and the".

    chipx86chipx86

    Instead of passing a default, we should fall back on what the app may have specified as the default when …

    chipx86chipx86

    No blank line.

    chipx86chipx86

    If we make use of siteconfig defaults, this information can be encoded there, rather than being hard-coded here.

    chipx86chipx86

    "human-readable"

    chipx86chipx86

    This sounds like the template will be rendering an image, but it's the HTML tags for an image.

    chipx86chipx86

    Extra "the"

    chipx86chipx86

    Alignment issue.

    chipx86chipx86

    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] = …

    chipx86chipx86

    Alignment issue.

    chipx86chipx86

    Let's change this to: raise NotImplementedError( '%r must implement get_avatar_urls_uncached().' % self.__class__ ) That way, when there are lots spewing …

    chipx86chipx86

    80 is the default for gravatars, but we care more about the caller. The caller should expect consistency in sizes, …

    chipx86chipx86

    We could support all ##x image sizes by doing: <img {% for key, url in urls.items %}{% if key|startswith:"@" %}{% …

    chipx86chipx86

    We should probably raise a ValueError if fmt is empty, instead of asserting, and specify the class and error name, …

    chipx86chipx86

    'mark_safe' imported but unused

    reviewbotreviewbot

    Description on the next line.

    chipx86chipx86

    The else is implied, so you can remove it. The first part is a guard, and doesn't have to be …

    chipx86chipx86

    You can remove the blank line here.

    chipx86chipx86

    This is already saved below. Also, are there cases where we don't need to save?

    chipx86chipx86

    Thinking this should be avatar_id. Some of our older stuff uses id (which is a reserved word), and most of …

    chipx86chipx86

    This is going to be a SafeText.

    chipx86chipx86

    Blank line not needed.

    chipx86chipx86

    Having a hard time parsing this. Maybe "The keyword arguments passed to the error-specific formatting string."

    chipx86chipx86

    No need for None.

    chipx86chipx86

    Let's include the class name in this (using __class__).

    chipx86chipx86

    No blank line.

    chipx86chipx86

    We shouldn't assume anything about the safeness of the URLs. If the URL has a & in it, then it'll …

    chipx86chipx86
    reviewbot
    1. 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
      
      
    2. djblets/avatars/errors.py (Diff revision 1)
       
       
      Show all issues
      Col: 1
       W391 blank line at end of file
      
    3. djblets/avatars/tests.py (Diff revision 1)
       
       
      Show all issues
      Col: 21
       E128 continuation line under-indented for visual indent
      
    4. djblets/avatars/tests.py (Diff revision 1)
       
       
      Show all issues
      Col: 21
       E128 continuation line under-indented for visual indent
      
    5. djblets/avatars/tests.py (Diff revision 1)
       
       
      Show all issues
      Col: 21
       E128 continuation line under-indented for visual indent
      
    6. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. djblets/avatars/tests.py (Diff revision 2)
       
       
      Show all issues
      Col: 1
       E303 too many blank lines (3)
      
    3. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. 
        
    david
    1. 
        
    2. djblets/avatars/errors.py (Diff revision 3)
       
       
      Show all issues

      The suspense is killing me.

    3. djblets/avatars/registry.py (Diff revision 3)
       
       
      Show all issues

      Blank line after this.

    4. djblets/avatars/registry.py (Diff revision 3)
       
       
      Show all issues

      Blank line after this.

    5. djblets/avatars/registry.py (Diff revision 3)
       
       
      Show all issues

      Missing the "Yields" label.

    6. djblets/avatars/registry.py (Diff revision 3)
       
       
      Show all issues

      Missing "Raises"

    7. djblets/avatars/services/base.py (Diff revision 3)
       
       
      Show all issues

      Should we include 3x for future proofing?

      1. If we do, we should say it may contain a 3x key. I suppose I should update the JS as well to support 3x avatars.

    8. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. 
        
    chipx86
    1. 
        
    2. djblets/avatars/registry.py (Diff revision 4)
       
       
      Show all issues

      This is wordy. Can we drop the first "services"? Maybe just "avatars_enabled_services" and "avatars_default_service"?

    3. djblets/avatars/registry.py (Diff revision 4)
       
       
       
       
      Show all issues

      The set shouldn't be there.

    4. djblets/avatars/registry.py (Diff revision 4)
       
       
       
      Show all issues

      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.

    5. djblets/avatars/registry.py (Diff revision 4)
       
       
      Show all issues

      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, subclassing ItemLookupError, 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.

    6. djblets/avatars/registry.py (Diff revision 4)
       
       
      Show all issues

      Needs the full path. Same with others below.

    7. djblets/avatars/registry.py (Diff revision 4)
       
       
      Show all issues

      There's an extra "and the".

    8. djblets/avatars/registry.py (Diff revision 4)
       
       
      Show all issues

      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.

    9. djblets/avatars/registry.py (Diff revision 4)
       
       
       
       
      Show all issues

      No blank line.

    10. djblets/avatars/registry.py (Diff revision 4)
       
       
       
       
       
       
       
      Show all issues

      If we make use of siteconfig defaults, this information can be encoded there, rather than being hard-coded here.

      1. It ends up being hardcoded somewhere. I'd rather it be hardcoded in the get_defaults method because thats where registry defaults come from elsewhere.

    11. djblets/avatars/services/base.py (Diff revision 4)
       
       
      Show all issues

      "human-readable"

    12. djblets/avatars/services/base.py (Diff revision 4)
       
       
      Show all issues

      This sounds like the template will be rendering an image, but it's the HTML tags for an image.

    13. djblets/avatars/services/base.py (Diff revision 4)
       
       
      Show all issues

      Extra "the"

    14. djblets/avatars/services/base.py (Diff revision 4)
       
       
       
      Show all issues

      Alignment issue.

    15. djblets/avatars/services/base.py (Diff revision 4)
       
       
       
       
       
       
      Show all issues

      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
      
    16. djblets/avatars/services/base.py (Diff revision 4)
       
       
       
      Show all issues

      Alignment issue.

    17. djblets/avatars/services/base.py (Diff revision 4)
       
       
      Show all issues

      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.)

    18. djblets/avatars/services/gravatar.py (Diff revision 4)
       
       
      Show all issues

      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 in AvatarService, simply not take a default value for size.

    19. Show all issues

      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.)

      1. I'm going to update this to use srcset :)

    20. djblets/registries/registry.py (Diff revision 4)
       
       
       
       
      Show all issues

      We should probably raise a ValueError if fmt 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.

    21. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. 
        
    david
    1. This looks good to me now. May want to get Christian to give it another look too, since it's pretty big.

    2. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. djblets/avatars/services/gravatar.py (Diff revision 7)
       
       
      Show all issues
       'mark_safe' imported but unused
      
    3. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. 
        
    brennie
    david
    1. Ship It!
    2. 
        
    chipx86
    1. Huh, was sure I published this...

    2. djblets/avatars/registry.py (Diff revision 8)
       
       
       
      Show all issues

      Description on the next line.

    3. djblets/avatars/registry.py (Diff revision 8)
       
       
       
       
       
       
      Show all issues

      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.

    4. djblets/avatars/registry.py (Diff revision 8)
       
       
       
       
      Show all issues

      You can remove the blank line here.

    5. djblets/avatars/registry.py (Diff revision 8)
       
       
      Show all issues

      This is already saved below.

      Also, are there cases where we don't need to save?

    6. djblets/avatars/services/base.py (Diff revision 8)
       
       
      Show all issues

      Thinking this should be avatar_id. Some of our older stuff uses id (which is a reserved word), and most of our newer stuff uses things like avatar_id.

    7. djblets/avatars/services/base.py (Diff revision 8)
       
       
      Show all issues

      This is going to be a SafeText.

    8. djblets/avatars/tests.py (Diff revision 8)
       
       
       
       
      Show all issues

      Blank line not needed.

    9. djblets/registries/registry.py (Diff revision 8)
       
       
       
      Show all issues

      Having a hard time parsing this. Maybe "The keyword arguments passed to the error-specific formatting string."

    10. djblets/registries/registry.py (Diff revision 8)
       
       
      Show all issues

      No need for None.

    11. djblets/registries/registry.py (Diff revision 8)
       
       
      Show all issues

      Let's include the class name in this (using __class__).

    12. djblets/static/djblets/js/jquery.gravy.retina.js (Diff revision 8)
       
       
       
       
      Show all issues

      No blank line.

    13. djblets/util/templatetags/djblets_images.py (Diff revision 8)
       
       
       
       
       
       
      Show all issues

      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 ...
          ))
      
      1. There's actually another place like this where we assume safety of the gravatar URL, whereas the old templates I don't think did that? I don't think we shouldn't assume safety of those URLs, but we should test that.

      2. No they won't.

        In [3]: from django.utils.html import mark_safe
        
        In [4]: mark_safe('http://foo/y?a=b&c=d')
        
        Out[4]: 'http://foo/y?a=b&c=d'
        

        Also, documentation explicitly states that all URLs returned from avatar services must be marked safe text, so this should be fine.

        Get back to me re: this and we can hash it out on Monday.

      3. I really have no earthly idea what I was thinking when I wrote that. I don't think I read the code I thought I read.

    14. 
        
    brennie
    reviewbot
    1. 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
      
      
    2. 
        
    brennie
    Review request changed
    Status:
    Completed
    Change Summary:
    Pushed to release-0.10.x (d940248)