# -*- coding: utf-8 -*-
"""
flaskbb.utils
~~~~~~~~~~~~~~~~~~~~
A few utils that are used by flaskbb
:copyright: (c) 2013 by the FlaskBB Team.
:license: BSD, see LICENSE for more details.
"""
import random
import time
from datetime import datetime, timedelta
from flask import current_app
from sqlalchemy import types
from sqlalchemy.ext.mutable import Mutable
from wtforms.widgets.core import Select, HTMLString, html_params
from postmarkup import render_bbcode
from flaskbb.extensions import redis
def mark_online(user_id, guest=False):
"""
Source: http://flask.pocoo.org/snippets/71/
"""
now = int(time.time())
expires = now + (current_app.config['ONLINE_LAST_MINUTES'] * 60) + 10
if guest:
all_users_key = 'online-guests/%d' % (now // 60)
user_key = 'guest-activity/%s' % user_id
else:
all_users_key = 'online-users/%d' % (now // 60)
user_key = 'user-activity/%s' % user_id
p = redis.pipeline()
p.sadd(all_users_key, user_id)
p.set(user_key, now)
p.expireat(all_users_key, expires)
p.expireat(user_key, expires)
p.execute()
def get_last_user_activity(user_id, guest=False):
if guest:
last_active = redis.get('guest-activity/%s' % user_id)
else:
last_active = redis.get('user-activity/%s' % user_id)
if last_active is None:
return None
return datetime.utcfromtimestamp(int(last_active))
def get_online_users(guest=False):
current = int(time.time()) // 60
minutes = xrange(current_app.config['ONLINE_LAST_MINUTES'])
if guest:
return redis.sunion(['online-guests/%d' % (current - x)
for x in minutes])
return redis.sunion(['online-users/%d' % (current - x)
for x in minutes])
def check_perm(user, perm, forum, post_user_id=None):
"""
Checks if the `user` has a specified `perm` in the `forum`
If post_user_id is provided, it will also check if the user
has created the post
"""
if can_moderate(user, forum):
return True
if post_user_id and user.is_authenticated():
return user.permissions[perm] and user.id == post_user_id
return user.permissions[perm]
def can_moderate(user, forum):
"""
Checks if a user can moderate a forum
He needs to be super moderator or a moderator of the
specified `forum`
"""
if user.permissions['mod'] and user.id in forum.moderators:
return True
return user.permissions['super_mod'] or user.permissions['admin']
def perm_edit_post(user, post_user_id, forum):
"""
Check if the post can be edited by the user
"""
return check_perm(user=user, perm='editpost', forum=forum,
post_user_id=post_user_id)
def perm_delete_post(user, post_user_id, forum):
"""
Check if the post can be deleted by the user
"""
return check_perm(user=user, perm='deletepost', forum=forum,
post_user_id=post_user_id)
def perm_delete_topic(user, post_user_id, forum):
"""
Check if the topic can be deleted by the user
"""
return check_perm(user=user, perm='deletetopic', forum=forum,
post_user_id=post_user_id)
def perm_post_reply(user, forum):
"""
Check if the user is allowed to post in the forum
"""
return check_perm(user=user, perm='postreply', forum=forum)
def perm_post_topic(user, forum):
"""
Check if the user is allowed to create a new topic in the forum
"""
return check_perm(user=user, perm='posttopic', forum=forum)
def crop_title(title):
"""
Crops the title to a specified length
"""
length = current_app.config['TITLE_LENGTH']
if len(title) > length:
return title[:length] + "..."
return title
def render_markup(text):
return render_bbcode(text)
def is_online(user):
return user.lastseen >= time_diff()
def time_diff():
now = datetime.utcnow()
diff = now - timedelta(minutes=current_app.config['ONLINE_LAST_MINUTES'])
return diff
def format_date(value, format='%Y-%m-%d'):
"""
Returns a formatted time string
"""
return value.strftime(format)
def time_since(value):
return time_delta_format(value)
def time_delta_format(dt, default=None):
"""
Returns string representing "time since" e.g.
3 days ago, 5 hours ago etc.
Ref: https://bitbucket.org/danjac/newsmeme/src/a281babb9ca3/newsmeme/
"""
if default is None:
default = 'just now'
now = datetime.utcnow()
diff = now - dt
periods = (
(diff.days / 365, 'year', 'years'),
(diff.days / 30, 'month', 'months'),
(diff.days / 7, 'week', 'weeks'),
(diff.days, 'day', 'days'),
(diff.seconds / 3600, 'hour', 'hours'),
(diff.seconds / 60, 'minute', 'minutes'),
(diff.seconds, 'second', 'seconds'),
)
for period, singular, plural in periods:
if not period:
continue
if period == 1:
return u'%d %s ago' % (period, singular)
else:
return u'%d %s ago' % (period, plural)
return default
class DenormalizedText(Mutable, types.TypeDecorator):
"""
Stores denormalized primary keys that can be
accessed as a set.
:param coerce: coercion function that ensures correct
type is returned
:param separator: separator character
Source: https://github.com/imwilsonxu/fbone/blob/master/fbone/user/models.py#L13-L45
"""
impl = types.Text
def __init__(self, coerce=int, separator=" ", **kwargs):
self.coerce = coerce
self.separator = separator
super(DenormalizedText, self).__init__(**kwargs)
def process_bind_param(self, value, dialect):
if value is not None:
items = [str(item).strip() for item in value]
value = self.separator.join(item for item in items if item)
return value
def process_result_value(self, value, dialect):
if not value:
return set()
return set(self.coerce(item) for item in value.split(self.separator))
def copy_value(self, value):
return set(value)
class SelectDateWidget(object):
"""
Renders a DateTime field with 3 selects.
For more information see: http://stackoverflow.com/a/14664504
"""
FORMAT_CHOICES = {
'%d': [(x, str(x)) for x in range(1, 32)],
'%m': [(x, str(x)) for x in range(1, 13)]
}
FORMAT_CLASSES = {
'%d': 'select_date_day',
'%m': 'select_date_month',
'%Y': 'select_date_year'
}
def __init__(self, years=range(1930, datetime.utcnow().year+1)):
super(SelectDateWidget, self).__init__()
self.FORMAT_CHOICES['%Y'] = [(x, str(x)) for x in years]
def __call__(self, field, **kwargs):
field_id = kwargs.pop('id', field.id)
html = []
allowed_format = ['%d', '%m', '%Y']
for format in field.format.split():
if (format in allowed_format):
choices = self.FORMAT_CHOICES[format]
id_suffix = format.replace('%', '-')
id_current = field_id + id_suffix
kwargs['class'] = self.FORMAT_CLASSES[format]
try:
del kwargs['placeholder']
except:
pass
html.append('')
else:
html.append(format)
html.append(
"""""".format(
html_params(name=field.name, id=id_current, **kwargs)))
html.append(' ')
return HTMLString(''.join(html))