# Users
(r"/users", users.IndexHandler),
- (r"/users/([\w]+)", users.ShowHandler),
+ (r"/users/([a-z_][a-z0-9_-]{0,31})", users.ShowHandler),
+ (r"/users/([a-z_][a-z0-9_-]{0,31})\.jpg", users.AvatarHandler),
# RSS feed
(r"/news.rss", tornado.web.RedirectHandler, { "url" : "https://blog.ipfire.org/feed.xml" }),
(r"/groups/([a-z_][a-z0-9_-]{0,31})", people.GroupHandler),
(r"/register", auth.RegisterHandler),
(r"/search", people.SearchHandler),
- (r"/users/([a-z_][a-z0-9_-]{0,31})\.jpg", people.AvatarHandler),
(r"/users/([a-z_][a-z0-9_-]{0,31})/calls/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})", people.CallHandler),
(r"/users/([a-z_][a-z0-9_-]{0,31})/calls(?:/(\d{4}-\d{2}-\d{2}))?", people.CallsHandler),
(r"/users/([a-z_][a-z0-9_-]{0,31})/edit", people.UserEditHandler),
#!/usr/bin/python
-import PIL
import datetime
-import imghdr
-import io
import ldap
-import logging
-import os.path
import tornado.web
import urllib.parse
from . import base
from . import ui_modules
-COLOUR_LIGHT = (237,232,232)
-COLOUR_DARK = (49,53,60)
-
class IndexHandler(auth.CacheMixin, base.BaseHandler):
@tornado.web.authenticated
def get(self):
self.render("people/index.html")
-class AvatarHandler(base.BaseHandler):
- def get(self, uid):
- # Get the desired size of the avatar file
- size = self.get_argument("size", None)
-
- try:
- size = int(size)
- except (TypeError, ValueError):
- size = None
-
- logging.debug("Querying for avatar of %s" % uid)
-
- # Fetch user account
- account = self.backend.accounts.get_by_uid(uid)
- if not account:
- raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
-
- # Allow downstream to cache this for a year
- self.set_expires(31536000)
-
- # Resize avatar
- avatar = account.get_avatar(size)
-
- # If there is no avatar, we serve a default image
- if not avatar:
- logging.debug("No avatar uploaded for %s" % account)
-
- # Generate a random avatar with only one letter
- avatar = self._get_avatar(account, size=size)
-
- # Guess content type
- type = imghdr.what(None, avatar)
-
- # Set headers about content
- self.set_header("Content-Disposition", "inline; filename=\"%s.%s\"" % (account.uid, type))
- self.set_header("Content-Type", "image/%s" % type)
-
- # Deliver payload
- self.finish(avatar)
-
- def _get_avatar(self, account, size=None, **args):
- letter = ("%s" % account)[0].upper()
-
- if size is None:
- size = 256
-
- # The generated avatar cannot be larger than 1024px
- if size >= 2048:
- size = 2048
-
- # Cache key
- key = "avatar:letter:%s:%s" % (letter, size)
-
- # Fetch avatar from the cache
- avatar = self.memcached.get(key)
- if not avatar:
- avatar = self._make_avatar(letter, size=size, **args)
-
- # Cache for forever
- self.memcached.set(key, avatar)
-
- return avatar
-
- def _make_avatar(self, letter, format="PNG", size=None, **args):
- # Generate an image of the correct size
- image = PIL.Image.new("RGBA", (size, size), COLOUR_LIGHT)
-
- # Have a canvas
- draw = PIL.ImageDraw.Draw(image)
-
- # Load font
- font = PIL.ImageFont.truetype(os.path.join(
- self.application.settings.get("static_path", ""),
- "fonts/Prompt-Bold.ttf"
- ), size, encoding="unic")
-
- # Determine size of the printed letter
- w, h = font.getsize(letter)
-
- # Mukta seems to be very broken and the height needs to be corrected
- h //= 0.7
-
- # Draw the letter in the center
- draw.text(((size - w) / 2, (size - h) / 2), letter,
- font=font, fill=COLOUR_DARK)
-
- with io.BytesIO() as f:
- # If writing out the image does not work with optimization,
- # we try to write it out without any optimization.
- try:
- image.save(f, format, optimize=True, **args)
- except:
- image.save(f, format, **args)
-
- return f.getvalue()
-
-
class CallsHandler(auth.CacheMixin, base.BaseHandler):
@tornado.web.authenticated
def get(self, uid, date=None):
#!/usr/bin/python
+import PIL
+import imghdr
+import io
+import logging
+import os.path
import tornado.web
from . import auth
from . import base
from . import ui_modules
+COLOUR_LIGHT = (237,232,232)
+COLOUR_DARK = (49,53,60)
+
class IndexHandler(auth.CacheMixin, base.BaseHandler):
@tornado.web.authenticated
def get(self):
self.render("users/show.html", account=account)
+class AvatarHandler(base.BaseHandler):
+ def get(self, uid):
+ # Get the desired size of the avatar file
+ size = self.get_argument("size", None)
+
+ try:
+ size = int(size)
+ except (TypeError, ValueError):
+ size = None
+
+ logging.debug("Querying for avatar of %s" % uid)
+
+ # Fetch user account
+ account = self.backend.accounts.get_by_uid(uid)
+ if not account:
+ raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
+
+ # Allow downstream to cache this for a year
+ self.set_expires(31536000)
+
+ # Resize avatar
+ avatar = account.get_avatar(size)
+
+ # If there is no avatar, we serve a default image
+ if not avatar:
+ logging.debug("No avatar uploaded for %s" % account)
+
+ # Generate a random avatar with only one letter
+ avatar = self._get_avatar(account, size=size)
+
+ # Guess content type
+ type = imghdr.what(None, avatar)
+
+ # If we could not guess the type, we will try something else
+ if not type:
+ # Could this be an SVG file?
+ if avatar.startswith(b"<"):
+ type = "svg+xml"
+
+ # Set headers about content
+ self.set_header("Content-Disposition", "inline; filename=\"%s.%s\"" % (account.uid, type))
+ if type:
+ self.set_header("Content-Type", "image/%s" % type)
+
+ # Deliver payload
+ self.finish(avatar)
+
+ def _get_avatar(self, account, size=None, **args):
+ letter = ("%s" % account)[0].upper()
+
+ if size is None:
+ size = 256
+
+ # The generated avatar cannot be larger than 1024px
+ if size >= 2048:
+ size = 2048
+
+ # Cache key
+ key = "avatar:letter:%s:%s" % (letter, size)
+
+ # Fetch avatar from the cache
+ avatar = self.memcached.get(key)
+ if not avatar:
+ avatar = self._make_avatar(letter, size=size, **args)
+
+ # Cache for forever
+ self.memcached.set(key, avatar)
+
+ return avatar
+
+ def _make_avatar(self, letter, format="PNG", size=None, **args):
+ # Generate an image of the correct size
+ image = PIL.Image.new("RGBA", (size, size), COLOUR_LIGHT)
+
+ # Have a canvas
+ draw = PIL.ImageDraw.Draw(image)
+
+ # Load font
+ font = PIL.ImageFont.truetype(os.path.join(
+ self.application.settings.get("static_path", ""),
+ "fonts/Prompt-Bold.ttf"
+ ), size, encoding="unic")
+
+ # Determine size of the printed letter
+ w, h = font.getsize(letter)
+
+ # Mukta seems to be very broken and the height needs to be corrected
+ h //= 0.7
+
+ # Draw the letter in the center
+ draw.text(((size - w) / 2, (size - h) / 2), letter,
+ font=font, fill=COLOUR_DARK)
+
+ with io.BytesIO() as f:
+ # If writing out the image does not work with optimization,
+ # we try to write it out without any optimization.
+ try:
+ image.save(f, format, optimize=True, **args)
+ except:
+ image.save(f, format, **args)
+
+ return f.getvalue()
+
+
class ListModule(ui_modules.UIModule):
def render(self, accounts, show_created_at=False):
return self.render_string("users/modules/list.html", accounts=accounts,