<commit_before>#!/usr/bin/env python

###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

"""
Various utility commands, mostly useful for manipulating nested commands.
"""

__revision__ = "$Id$"

import plugins

import string

import utils
import ircmsgs
import ircutils
import privmsgs
import callbacks

class Utilities(callbacks.Privmsg):
    def ignore(self, irc, msg, args):
        """requires no arguments

        Does nothing.  Useful sometimes for sequencing commands when you don't
        care about their non-error return values.
        """
        pass

    def success(self, irc, msg, args):
        """requires no arguments

        Does nothing except to reply with a success message.  This is useful
        when you want to run multiple commands as nested commands, and don't
        care about their output as long as they're successful.  An error, of
        course, will break out of this command.
        """
        irc.replySuccess()

    def strjoin(self, irc, msg, args):
        """<separator> <string 1> [<string> ...]

        Joins all the arguments together with <separator>.
        """
        sep = args.pop(0)
        irc.reply(sep.join(args))

    def strtranslate(self, irc, msg, args):
        """<chars to translate> <chars to replace those with> <text>

        Replaces <chars to translate> with <chars to replace those with> in
        <text>.  The first and second arguments must necessarily be the same
        length.
        """
        (bad, good, text) = privmsgs.getArgs(args, required=3)
        irc.reply(text.translate(string.maketrans(bad, good)))

    def strupper(self, irc, msg, args):
        """<text>

        Returns <text> uppercased.
        """
        irc.reply(privmsgs.getArgs(args).upper())

    def strlower(self, irc, msg, args):
        """<text>

        Returns <text> lowercased.
        """
        irc.reply(privmsgs.getArgs(args).lower())

    def strlen(self, irc, msg, args):
        """<text>

        Returns the length of <text>.
        """
        total = 0
        for arg in args:
            total += len(arg)
        total += len(args)-1 # spaces between the arguments.
        irc.reply(str(total))

    def repr(self, irc, msg, args):
        """<text>

        Returns the text surrounded by double quotes.
        """
        text = privmsgs.getArgs(args)
        irc.reply(utils.dqrepr(text))

    def strconcat(self, irc, msg, args):
        """<string 1> <string 2>

        Concatenates two strings.  Do keep in mind that this is *not* the same
        thing as strjoin "", since if <string 2> contains spaces, they won't be
        removed by strconcat.
        """
        (first, second) = privmsgs.getArgs(args, required=2)
        irc.reply(first+second)

    def echo(self, irc, msg, args):
        """takes any number of arguments

        Returns the arguments given it.
        """
        if not args:
            raise callbacks.ArgumentError
        text = privmsgs.getArgs(args)
        text = plugins.standardSubstitute(irc, msg, text)
        irc.reply(text, prefixName=False)

    def re(self, irc, msg, args):
        """<regexp> <text>

        If <regexp> is of the form m/regexp/flags, returns the portion of
        <text> that matches the regexp.  If <regexp> is of the form
        s/regexp/replacement/flags, returns the result of applying such a
        regexp to <text>
        """
        (regexp, text) = privmsgs.getArgs(args, required=2)
        self.log.info('re command called with regexp %r from %s' %
                      (regexp, msg.prefix))
        if len(regexp) > 512:
            irc.error('Your regexp is just plain too long.')
            return
        f = None
        try:
            r = utils.perlReToPythonRe(regexp)
            f = lambda s: r.search(s) and r.search(s).group(0) or ''
        except ValueError, e:
            try:
                f = utils.perlReToReplacer(regexp)
            except ValueError, e:
                irc.error('Invalid regexp: %s' % e.args[0])
                return
            if f is None:
                irc.error('Invalid regexp: %s' % e.args[0])
                return
        if f('') and len(f(' ')) > len(f(''))+1: # Matches the empty string.
            s = 'You probably don\'t want to match the empty string.'
            irc.error(s)
        else:
            irc.reply(f(text))
    re = privmsgs.checkCapability(re, 'trusted')

    def apply(self, irc, msg, args):
        """<command> <text>

        Tokenizes <text> and calls <command> with the resulting arguments.
        """
        if not args:
            raise callbacks.ArgumentError
        command = args.pop(0)
        args = [token and token or '""' for token in args]
        text = privmsgs.getArgs(args)
        commands = command.split()
        commands = map(callbacks.canonicalName, commands)
        tokens = callbacks.tokenize(text)
        allTokens = commands + tokens
        print '***', allTokens
        Owner = irc.getCallback('Owner')
        Owner.processTokens(irc, msg, allTokens)

Class = Utilities
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
<commit_msg>Updated help for echo.<commit_after>#!/usr/bin/env python

###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

"""
Various utility commands, mostly useful for manipulating nested commands.
"""

__revision__ = "$Id$"

import plugins

import string

import utils
import ircmsgs
import ircutils
import privmsgs
import callbacks

class Utilities(callbacks.Privmsg):
    def ignore(self, irc, msg, args):
        """requires no arguments

        Does nothing.  Useful sometimes for sequencing commands when you don't
        care about their non-error return values.
        """
        pass

    def success(self, irc, msg, args):
        """requires no arguments

        Does nothing except to reply with a success message.  This is useful
        when you want to run multiple commands as nested commands, and don't
        care about their output as long as they're successful.  An error, of
        course, will break out of this command.
        """
        irc.replySuccess()

    def strjoin(self, irc, msg, args):
        """<separator> <string 1> [<string> ...]

        Joins all the arguments together with <separator>.
        """
        sep = args.pop(0)
        irc.reply(sep.join(args))

    def strtranslate(self, irc, msg, args):
        """<chars to translate> <chars to replace those with> <text>

        Replaces <chars to translate> with <chars to replace those with> in
        <text>.  The first and second arguments must necessarily be the same
        length.
        """
        (bad, good, text) = privmsgs.getArgs(args, required=3)
        irc.reply(text.translate(string.maketrans(bad, good)))

    def strupper(self, irc, msg, args):
        """<text>

        Returns <text> uppercased.
        """
        irc.reply(privmsgs.getArgs(args).upper())

    def strlower(self, irc, msg, args):
        """<text>

        Returns <text> lowercased.
        """
        irc.reply(privmsgs.getArgs(args).lower())

    def strlen(self, irc, msg, args):
        """<text>

        Returns the length of <text>.
        """
        total = 0
        for arg in args:
            total += len(arg)
        total += len(args)-1 # spaces between the arguments.
        irc.reply(str(total))

    def repr(self, irc, msg, args):
        """<text>

        Returns the text surrounded by double quotes.
        """
        text = privmsgs.getArgs(args)
        irc.reply(utils.dqrepr(text))

    def strconcat(self, irc, msg, args):
        """<string 1> <string 2>

        Concatenates two strings.  Do keep in mind that this is *not* the same
        thing as strjoin "", since if <string 2> contains spaces, they won't be
        removed by strconcat.
        """
        (first, second) = privmsgs.getArgs(args, required=2)
        irc.reply(first+second)

    def echo(self, irc, msg, args):
        """takes any number of arguments

        Returns the arguments given it.  Uses our standard substitute on the
        string(s) given to it; $nick (or $who), $randomNick, $randomInt,
        $botnick, $channel, $user, $host, $today, $now, and $randomDate are all
        handled appropriately.
        """
        if not args:
            raise callbacks.ArgumentError
        text = privmsgs.getArgs(args)
        text = plugins.standardSubstitute(irc, msg, text)
        irc.reply(text, prefixName=False)

    def re(self, irc, msg, args):
        """<regexp> <text>

        If <regexp> is of the form m/regexp/flags, returns the portion of
        <text> that matches the regexp.  If <regexp> is of the form
        s/regexp/replacement/flags, returns the result of applying such a
        regexp to <text>
        """
        (regexp, text) = privmsgs.getArgs(args, required=2)
        self.log.info('re command called with regexp %r from %s' %
                      (regexp, msg.prefix))
        if len(regexp) > 512:
            irc.error('Your regexp is just plain too long.')
            return
        f = None
        try:
            r = utils.perlReToPythonRe(regexp)
            f = lambda s: r.search(s) and r.search(s).group(0) or ''
        except ValueError, e:
            try:
                f = utils.perlReToReplacer(regexp)
            except ValueError, e:
                irc.error('Invalid regexp: %s' % e.args[0])
                return
            if f is None:
                irc.error('Invalid regexp: %s' % e.args[0])
                return
        if f('') and len(f(' ')) > len(f(''))+1: # Matches the empty string.
            s = 'You probably don\'t want to match the empty string.'
            irc.error(s)
        else:
            irc.reply(f(text))
    re = privmsgs.checkCapability(re, 'trusted')

    def apply(self, irc, msg, args):
        """<command> <text>

        Tokenizes <text> and calls <command> with the resulting arguments.
        """
        if not args:
            raise callbacks.ArgumentError
        command = args.pop(0)
        args = [token and token or '""' for token in args]
        text = privmsgs.getArgs(args)
        commands = command.split()
        commands = map(callbacks.canonicalName, commands)
        tokens = callbacks.tokenize(text)
        allTokens = commands + tokens
        print '***', allTokens
        Owner = irc.getCallback('Owner')
        Owner.processTokens(irc, msg, allTokens)

Class = Utilities
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
<|endoftext|>


<commit_before>#   Copyright 2011 Paul Voccio
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Pyhole Launchpad Plugin"""

from launchpadlib.launchpad import Launchpad as LP

from pyhole import plugin
from pyhole import utils

class Launchpad(plugin.Plugin):
    """Provide access to the Launchpad API"""

    def __init__(self, irc):
        self.irc = irc
        self.launchpad = LP.login_anonymously("pyhole", "production",
                                              self.irc.cache)

    @plugin.hook_add_command("lbugs")
    @utils.spawn
    def lbugs(self, params=None, **kwargs):
        """Launchpad bugs for a team (ex: .lbugs <project> <team>|<user>)"""
        if params:
            project, team = params.split(" ", 2)
            try:
                members = self.launchpad.people[team]
                proj = self.launchpad.projects[project]

                # Find a single member
                self._find_bugs(members, proj, True)

                # Find everyone on the team
                for person in members.members:
                    self.irc.log.debug("LP: %s" % person.display_name)
                    self._find_bugs(person, proj)
            except KeyError:
                self.irc.reply("Unable to find user '%s' in Launchpad" % team)
        else:
            self.irc.reply(self.lbugs.__doc__)

    @plugin.hook_add_keyword("lp")
    @utils.spawn
    def keyword_lp(self, params=None, **kwargs):
        """Retrieve Launchpad bug information (ex: LP12345)"""
        if params:
            params = utils.ensure_int(params)

            if not params:
                return

            try:
                bug = self.launchpad.bugs[params]
                task = bug.bug_tasks[len(bug.bug_tasks) - 1]

                self.irc.reply("LP Bug #%s: %s [Status: %s, Assignee: %s]"
                               " %s" % (bug.id, bug.title, task.status,
                                        self._find_name(task.assignee_link),
                                        bug.web_link))
            except Exception:
                return

    def _find_name(self, user):
        """Lookup a Launchpad user's display name"""
        return self.launchpad.people[user].display_name

    def _find_bugs(self, person, project, single=False):
        """Lookup Launchpad bugs"""
        bugs = project.searchTasks(assignee=person)
        if len(bugs):
            for bug in bugs:
                self.irc.reply("LP %s [Assignee: %s] %s" % (bug.title,
                                                            person.\
                                                            display_name,
                                                            bug.web_link))
        else:
            if single:
                self.irc.reply("No bugs found for %s" % (person.display_name))
<commit_msg>Fix Launchpad plugin issue.<commit_after>#   Copyright 2011 Paul Voccio
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Pyhole Launchpad Plugin"""

from launchpadlib.launchpad import Launchpad as LP

from pyhole import plugin
from pyhole import utils

class Launchpad(plugin.Plugin):
    """Provide access to the Launchpad API"""

    def __init__(self, irc):
        self.irc = irc
        self.launchpad = LP.login_anonymously("pyhole", "production",
                                              self.irc.cache)

    @plugin.hook_add_command("lbugs")
    @utils.spawn
    def lbugs(self, params=None, **kwargs):
        """Launchpad bugs for a team (ex: .lbugs <project> <team>|<user>)"""
        if params:
            project, team = params.split(" ", 2)
            try:
                members = self.launchpad.people[team]
                proj = self.launchpad.projects[project]

                # Find a single member
                self._find_bugs(members, proj, True)

                # Find everyone on the team
                for person in members.members:
                    self.irc.log.debug("LP: %s" % person.display_name)
                    self._find_bugs(person, proj)
            except KeyError:
                self.irc.reply("Unable to find user '%s' in Launchpad" % team)
        else:
            self.irc.reply(self.lbugs.__doc__)

    @plugin.hook_add_keyword("lp")
    @utils.spawn
    def keyword_lp(self, params=None, **kwargs):
        """Retrieve Launchpad bug information (ex: LP12345)"""
        if params:
            params = utils.ensure_int(params)

            if not params:
                return

            try:
                bug = self.launchpad.bugs[params]
                task = bug.bug_tasks[len(bug.bug_tasks) - 1]

                self.irc.reply("LP Bug #%s: %s [Status: %s, Assignee: %s]"
                               " %s" % (bug.id, bug.title, task.status,
                                        self._find_name(task.assignee_link),
                                        bug.web_link))
            except Exception:
                return

    def _find_name(self, user):
        """Lookup a Launchpad user's display name"""
        try:
            return self.launchpad.people[user].display_name
        except ValueError:
            return "None"

    def _find_bugs(self, person, project, single=False):
        """Lookup Launchpad bugs"""
        bugs = project.searchTasks(assignee=person)
        if len(bugs):
            for bug in bugs:
                self.irc.reply("LP %s [Assignee: %s] %s" % (bug.title,
                                                            person.\
                                                            display_name,
                                                            bug.web_link))
        else:
            if single:
                self.irc.reply("No bugs found for %s" % (person.display_name))
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

import re

class PluginError(exception):
    pass

class stdPlugin(object):
    u'''Permet de créer des plugins.'''

    #events = {'pubmsg': {'exclusive': True, 'command_namespace': 'say'}}
    events = {}

    def __init__(self, bot, conf):
        self.bot = bot
        self.conf = conf

    def on_pubmsg(self, serv, ev, helper):
        return False

    def on_privmsg(self, serv, ev, helper):
        return False

    def on_action(self, serv, ev, helper):
        return False

    def on_join(self, serv, ev, helper):
        return False

    def on_kick(self, serv, ev, helper):
        return False

    def on_cmd(self, serv, ev, command, args, helper):
        u'''Retourne l’aide correspondant à chaque commande.'''
        return False

    def say(self, serv, target, message):
        if message.startswith('/me '):
            serv.action(target, message[4:])
        else:
            serv.privmsg(target, message)
<commit_msg>fixed typo in stdPlugin<commit_after># -*- coding: utf-8 -*-

import re

class PluginError(Exception):
    pass

class stdPlugin(object):
    u'''Permet de créer des plugins.'''

    #events = {'pubmsg': {'exclusive': True, 'command_namespace': 'say'}}
    events = {}

    def __init__(self, bot, conf):
        self.bot = bot
        self.conf = conf

    def on_pubmsg(self, serv, ev, helper):
        return False

    def on_privmsg(self, serv, ev, helper):
        return False

    def on_action(self, serv, ev, helper):
        return False

    def on_join(self, serv, ev, helper):
        return False

    def on_kick(self, serv, ev, helper):
        return False

    def on_cmd(self, serv, ev, command, args, helper):
        u'''Retourne l’aide correspondant à chaque commande.'''
        return False

    def say(self, serv, target, message):
        if message.startswith('/me '):
            serv.action(target, message[4:])
        else:
            serv.privmsg(target, message)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# Author: Taylor Smith <taylor.smith@alkaline-ml.com>
#
# The pmdarima module

import os as _os

__version__ = "1.1.0-dev0"

try:
    # this var is injected in the setup build to enable
    # the retrieval of the version number without actually
    # importing the un-built submodules.
    __PMDARIMA_SETUP__
except NameError:
    __PMDARIMA_SETUP__ = False

if __PMDARIMA_SETUP__:
    import sys
    sys.stderr.write('Partial import of pmdarima during the build process.%s'
                     % _os.linesep)
else:
    # check that the build completed properly. This prints an informative
    # message in the case that any of the C code was not properly compiled.
    from . import __check_build

    # Stuff we want at top-level
    from .arima import auto_arima, ARIMA
    from .utils import acf, autocorr_plot, c, pacf, plot_acf, plot_pacf

    # Need these namespaces at the top so they can be used like:
    # pm.datasets.load_wineind()
    from . import arima
    from . import datasets
    from . import utils

    __all__ = [
        # Namespaces we want exposed at top:
        'arima',
        'compat',
        'datasets',
        'utils',

        # Function(s) at top level
        'ARIMA',
        'acf',
        'autocorr_plot',
        'auto_arima',
        'c',
        'pacf',
        'plot_acf',
        'plot_pacf'
    ]

    # On first import, check the cache, warn if needed
    from ._config import _warn_for_cache_size

    _warn_for_cache_size()

    # Delete unwanted variables from global
    del _os
    # del _config  # don't delete in case user wants to amend it at top level
    del _warn_for_cache_size
    # del _config  # don't delete in case user wants to amend it at top level
    del __check_build
    del __PMDARIMA_SETUP__

def setup_module(module):
    import numpy as np
    import random

    _random_seed = int(np.random.uniform() * (2 ** 31 - 1))
    np.random.seed(_random_seed)
    random.seed(_random_seed)
<commit_msg>Bump version, remove dev tag<commit_after># -*- coding: utf-8 -*-
#
# Author: Taylor Smith <taylor.smith@alkaline-ml.com>
#
# The pmdarima module

import os as _os

__version__ = "1.1.0"

try:
    # this var is injected in the setup build to enable
    # the retrieval of the version number without actually
    # importing the un-built submodules.
    __PMDARIMA_SETUP__
except NameError:
    __PMDARIMA_SETUP__ = False

if __PMDARIMA_SETUP__:
    import sys
    sys.stderr.write('Partial import of pmdarima during the build process.%s'
                     % _os.linesep)
else:
    # check that the build completed properly. This prints an informative
    # message in the case that any of the C code was not properly compiled.
    from . import __check_build

    # Stuff we want at top-level
    from .arima import auto_arima, ARIMA
    from .utils import acf, autocorr_plot, c, pacf, plot_acf, plot_pacf

    # Need these namespaces at the top so they can be used like:
    # pm.datasets.load_wineind()
    from . import arima
    from . import datasets
    from . import utils

    __all__ = [
        # Namespaces we want exposed at top:
        'arima',
        'compat',
        'datasets',
        'utils',

        # Function(s) at top level
        'ARIMA',
        'acf',
        'autocorr_plot',
        'auto_arima',
        'c',
        'pacf',
        'plot_acf',
        'plot_pacf'
    ]

    # On first import, check the cache, warn if needed
    from ._config import _warn_for_cache_size

    _warn_for_cache_size()

    # Delete unwanted variables from global
    del _os
    # del _config  # don't delete in case user wants to amend it at top level
    del _warn_for_cache_size
    # del _config  # don't delete in case user wants to amend it at top level
    del __check_build
    del __PMDARIMA_SETUP__

def setup_module(module):
    import numpy as np
    import random

    _random_seed = int(np.random.uniform() * (2 ** 31 - 1))
    np.random.seed(_random_seed)
    random.seed(_random_seed)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
import cherrypy
import string
import posixpath
import random
import calendar
import datetime
import textwrap
import cgi

import pkg_resources
import jinja2.loaders
import pytz
from jaraco.util.numbers import ordinalth as th_it

import pmxbot.core
import pmxbot.logging
import pmxbot.util

jenv = jinja2.Environment(loader=jinja2.loaders.PackageLoader('pmxbot.web'))
TIMEOUT=10.0

colors = ["06F", "900", "093", "F0C", "C30", "0C9", "666", "C90", "C36",
	"F60", "639", "630", "966", "69C", "039", '7e1e9c', '15b01a', '0343df',
	'ff81c0', '653700', 'e50000', '029386', 'f97306', 'c20078', '75bbfd']
random.shuffle(colors)

def get_context():
	c = cherrypy.request.app.config['botconf']['config']
	d = dict(
		request = cherrypy.request,
		name = c.bot_nickname,
		config = c,
		base = c.web_base,
		logo = c.logo
	)
	return d

def make_anchor(line):
	time, nick = line
	return "%s.%s" % (str(time).replace(':', '.'), nick)

def pmon(month):
	"""
	P the month

	>>> pmon('2012-08')
	'August, 2012'
	"""
	year, month = month.split('-')
	return '{month_name}, {year}'.format(
		month_name = calendar.month_name[int(month)],
		year = year,
	)

def pday(dayfmt):
	"""
	P the day

	>>> pday('2012-08-24')
	'Friday the 24th'
	"""

	year, month, day = map(int, dayfmt.split('-'))
	return '{day} the {number}'.format(
		day = calendar.day_name[calendar.weekday(year, month, day)],
		number = th_it(day),
	)

rev_month = {}
for x in xrange(1, 13):
	rev_month[calendar.month_name[x]] = x

def sort_month_key(m):
	parts = m.split(',')
	parts[0] = rev_month[parts[0]]
	return parts[1], parts[0]

def log_db():
	return pmxbot.logging.Logger.from_URI(
		cherrypy.request.app.config['botconf']['config'].database)

class ChannelPage(object):
	def default(self, channel):
		page = jenv.get_template('channel.html')

		db = log_db()
		context = get_context()
		contents = db.get_channel_days(channel)
		months = {}
		for fn in sorted(contents, reverse=True):
			mon_des, day = fn.rsplit('-', 1)
			months.setdefault(pmon(mon_des), []).append((pday(fn), fn))
		sort_key = lambda v: sort_month_key(v[0])
		context['months'] = sorted(months.items(), key=sort_key, reverse=True)
		context['channel'] = channel
		return page.render(**context).encode('utf-8')
	default.exposed = True

class DayPage(object):
	def default(self, channel, day):
		page = jenv.get_template('day.html')
		db = log_db()
		context = get_context()
		day_logs = db.get_day_logs(channel, day)
		data = [(t, n, make_anchor((t, n)), cgi.escape(m))
			for (t,n,m) in day_logs]
		usernames = [x[1] for x in data]
		color_map = {}
		clrs = colors[:]
		for u in usernames:
			if u not in color_map:
				try:
					color = clrs.pop(0)
				except IndexError:
					color = "000"
				color_map[u] = color
		context['color_map'] = color_map
		context['history'] = data
		context['channel'] = channel
		context['pdate'] = "{pday} of {days}".format(
			pday = pday(day),
			days = pmon(day.rsplit('-', 1)[0]),
		)
		return page.render(**context).encode('utf-8')
	default.exposed = True

def karma_comma(karma_results):
	"""
	(say that 5 times fast)

	Take the results of a karma query (keys, value) and return the same
	result with the keys joined by commas.
	"""
	return [
		(', '.join(keys), value)
		for keys, value in karma_results
	]

class KarmaPage(object):
	def default(self, term=""):
		page = jenv.get_template('karma.html')
		context = get_context()
		karma = pmxbot.util.Karma.from_URI(
			cherrypy.request.app.config['botconf']['config'].database
		)
		term = term.strip()
		if term:
			context['lookup'] = (
				[karma_comma(res) for res in karma.search(term)]
				or [('NO RESULTS FOUND', '')]
			)
		context['top100'] = karma_comma(karma.list(select=100))
		context['bottom100'] = karma_comma(karma.list(select=-100))
		return page.render(**context).encode('utf-8')
	default.exposed = True

class SearchPage(object):
	def default(self, term=''):
		page = jenv.get_template('search.html')
		context = get_context()
		db = log_db()
		#db.text_factory = lambda x: unicode(x, "utf-8", "ignore")

		# a hack to enable the database to create anchors when building search
		#  results
		db.make_anchor = make_anchor

		if not term:
			raise cherrypy.HTTPRedirect(cherrypy.request.base)
		terms = term.strip().split()
		results = sorted(db.search(*terms), key=lambda x: x[1], reverse=True)
		context['search_results'] = results
		context['num_results'] = len(results)
		context['term'] = term
		return page.render(**context).encode('utf-8')
	default.exposed = True

class HelpPage(object):
	def __init__(self):
		self.run = False

	def default(self):
		page = jenv.get_template('help.html')
		context = get_context()
		if not self.run:
			self.commands = []
			self.contains = []
			pmxbot.core.initialize(context['config'])
			for typ, name, f, doc, channels, exclude, rate, priority in \
					sorted(pmxbot.core._handler_registry, key=lambda x: x[1]):
				if typ == 'command':
					aliases = sorted([x[1]
						for x in pmxbot.core._handler_registry
						if x[0] == 'alias' and x[2] == f])
					self.commands.append((name, doc, aliases))
				elif typ == 'contains':
					self.contains.append((name, doc))
			self.run = True
		context['commands'] = self.commands
		context['contains'] = self.contains
		return page.render(**context).encode('utf-8')
	default.exposed = True

class LegacyPage():
	"""
	Forwards legacy /day/{channel}/{date}#{time}.{nick} in local time to
	the proper page at /day (in UTC).
	"""
	timezone = pytz.timezone('US/Pacific')

	@cherrypy.expose
	def default(self, channel, date_s):
		"""
		Return a web page that will get the fragment out and pass it to
		us so we can parse it.
		"""
		return textwrap.dedent("""

		""").lstrip()

	@cherrypy.expose
	def forward(self, channel, date_s, fragment):
		"""
		Given an HREF in the legacy timezone, redirect to an href for UTC.
		"""
		time_s, sep, nick = fragment.rpartition('.')
		time = datetime.datetime.strptime(time_s, '%H.%M.%S')
		date = datetime.datetime.strptime(date_s, '%Y-%m-%d')
		dt = datetime.datetime.combine(date, time.time())
		loc_dt = self.timezone.localize(dt)
		utc_dt = loc_dt.astimezone(pytz.utc)
		url_fmt = '/day/{channel}/{target_date}#{target_time}.{nick}'
		raise cherrypy.HTTPRedirect(
			url_fmt.format(
				target_date = utc_dt.date().isoformat(),
				target_time = utc_dt.time().strftime('%H.%M.%S'),
				**vars()
				),
			301,
		)

class PmxbotPages(object):
	channel = ChannelPage()
	day = DayPage()
	karma = KarmaPage()
	search = SearchPage()
	help = HelpPage()
	legacy = LegacyPage()

	def default(self):
		page = jenv.get_template('index.html')
		db = log_db()
		context = get_context()
		chans = []
		for chan in sorted(db.list_channels(), key = string.lower):
			last = db.last_message(chan)
			summary = [
				chan,
				last['datetime'].strftime("%Y-%m-%d %H:%M"),
				last['datetime'].date(),
				last['datetime'].time(),
				last['nick'],
				cgi.escape(last['message'][:75]),
				make_anchor([last['datetime'].time(), last['nick']]),
			]
			chans.append(summary)
		context['chans'] = chans
		return page.render(**context).encode('utf-8')
	default.exposed = True

def patch_compat(config):
	"""
	Support older config values.
	"""
	if 'web_host' in config:
		config['host'] = config.pop('web_host')
	if 'web_port' in config:
		config['port'] = config.pop('web_port')

def startup(config):
	patch_compat(config)

	pmxbot.web.config.update(config)
	config = pmxbot.web.config
	if not config.web_base.startswith('/'):
		config['web_base'] = '/' + config.web_base
	if config.web_base.endswith('/'):
		config['web_base'] = config.web_base.rstrip('/')
	if 'logo' not in config:
		config['logo'] = posixpath.join(config.web_base, 'pmxbot.png')

	# Cherrypy configuration here
	app_conf = {
		'global': {
			'server.socket_port': config.port,
			'server.socket_host': config.host,
			#'tools.encode.on': True,
			'tools.encode.encoding': 'utf-8',
		},
		'/pmxbot.png': {
			'tools.staticfile.on': True,
			'tools.staticfile.filename': pkg_resources.resource_filename(
				'pmxbot.web', 'templates/pmxbot.png'),
		},
		'botconf': {'config': config},
	}

	cherrypy.quickstart(PmxbotPages(), config.web_base, config=app_conf)

def run():
	startup(pmxbot.core.get_args().config)

if __name__ == '__main__':
	run()
<commit_msg>Load the library extensions so the help is available for all commands.<commit_after># -*- coding: utf-8 -*-
import cherrypy
import string
import posixpath
import random
import calendar
import datetime
import textwrap
import cgi

import pkg_resources
import jinja2.loaders
import pytz
from jaraco.util.numbers import ordinalth as th_it

import pmxbot.core
import pmxbot.logging
import pmxbot.util

jenv = jinja2.Environment(loader=jinja2.loaders.PackageLoader('pmxbot.web'))
TIMEOUT=10.0

colors = ["06F", "900", "093", "F0C", "C30", "0C9", "666", "C90", "C36",
	"F60", "639", "630", "966", "69C", "039", '7e1e9c', '15b01a', '0343df',
	'ff81c0', '653700', 'e50000', '029386', 'f97306', 'c20078', '75bbfd']
random.shuffle(colors)

def get_context():
	c = cherrypy.request.app.config['botconf']['config']
	d = dict(
		request = cherrypy.request,
		name = c.bot_nickname,
		config = c,
		base = c.web_base,
		logo = c.logo
	)
	return d

def make_anchor(line):
	time, nick = line
	return "%s.%s" % (str(time).replace(':', '.'), nick)

def pmon(month):
	"""
	P the month

	>>> pmon('2012-08')
	'August, 2012'
	"""
	year, month = month.split('-')
	return '{month_name}, {year}'.format(
		month_name = calendar.month_name[int(month)],
		year = year,
	)

def pday(dayfmt):
	"""
	P the day

	>>> pday('2012-08-24')
	'Friday the 24th'
	"""

	year, month, day = map(int, dayfmt.split('-'))
	return '{day} the {number}'.format(
		day = calendar.day_name[calendar.weekday(year, month, day)],
		number = th_it(day),
	)

rev_month = {}
for x in xrange(1, 13):
	rev_month[calendar.month_name[x]] = x

def sort_month_key(m):
	parts = m.split(',')
	parts[0] = rev_month[parts[0]]
	return parts[1], parts[0]

def log_db():
	return pmxbot.logging.Logger.from_URI(
		cherrypy.request.app.config['botconf']['config'].database)

class ChannelPage(object):
	def default(self, channel):
		page = jenv.get_template('channel.html')

		db = log_db()
		context = get_context()
		contents = db.get_channel_days(channel)
		months = {}
		for fn in sorted(contents, reverse=True):
			mon_des, day = fn.rsplit('-', 1)
			months.setdefault(pmon(mon_des), []).append((pday(fn), fn))
		sort_key = lambda v: sort_month_key(v[0])
		context['months'] = sorted(months.items(), key=sort_key, reverse=True)
		context['channel'] = channel
		return page.render(**context).encode('utf-8')
	default.exposed = True

class DayPage(object):
	def default(self, channel, day):
		page = jenv.get_template('day.html')
		db = log_db()
		context = get_context()
		day_logs = db.get_day_logs(channel, day)
		data = [(t, n, make_anchor((t, n)), cgi.escape(m))
			for (t,n,m) in day_logs]
		usernames = [x[1] for x in data]
		color_map = {}
		clrs = colors[:]
		for u in usernames:
			if u not in color_map:
				try:
					color = clrs.pop(0)
				except IndexError:
					color = "000"
				color_map[u] = color
		context['color_map'] = color_map
		context['history'] = data
		context['channel'] = channel
		context['pdate'] = "{pday} of {days}".format(
			pday = pday(day),
			days = pmon(day.rsplit('-', 1)[0]),
		)
		return page.render(**context).encode('utf-8')
	default.exposed = True

def karma_comma(karma_results):
	"""
	(say that 5 times fast)

	Take the results of a karma query (keys, value) and return the same
	result with the keys joined by commas.
	"""
	return [
		(', '.join(keys), value)
		for keys, value in karma_results
	]

class KarmaPage(object):
	def default(self, term=""):
		page = jenv.get_template('karma.html')
		context = get_context()
		karma = pmxbot.util.Karma.from_URI(
			cherrypy.request.app.config['botconf']['config'].database
		)
		term = term.strip()
		if term:
			context['lookup'] = (
				[karma_comma(res) for res in karma.search(term)]
				or [('NO RESULTS FOUND', '')]
			)
		context['top100'] = karma_comma(karma.list(select=100))
		context['bottom100'] = karma_comma(karma.list(select=-100))
		return page.render(**context).encode('utf-8')
	default.exposed = True

class SearchPage(object):
	def default(self, term=''):
		page = jenv.get_template('search.html')
		context = get_context()
		db = log_db()
		#db.text_factory = lambda x: unicode(x, "utf-8", "ignore")

		# a hack to enable the database to create anchors when building search
		#  results
		db.make_anchor = make_anchor

		if not term:
			raise cherrypy.HTTPRedirect(cherrypy.request.base)
		terms = term.strip().split()
		results = sorted(db.search(*terms), key=lambda x: x[1], reverse=True)
		context['search_results'] = results
		context['num_results'] = len(results)
		context['term'] = term
		return page.render(**context).encode('utf-8')
	default.exposed = True

class HelpPage(object):
	def __init__(self):
		self.run = False

	def default(self):
		page = jenv.get_template('help.html')
		context = get_context()
		if not self.run:
			self.commands = []
			self.contains = []
			pmxbot.core.initialize(context['config'])
			for typ, name, f, doc, channels, exclude, rate, priority in \
					sorted(pmxbot.core._handler_registry, key=lambda x: x[1]):
				if typ == 'command':
					aliases = sorted([x[1]
						for x in pmxbot.core._handler_registry
						if x[0] == 'alias' and x[2] == f])
					self.commands.append((name, doc, aliases))
				elif typ == 'contains':
					self.contains.append((name, doc))
			self.run = True
		context['commands'] = self.commands
		context['contains'] = self.contains
		return page.render(**context).encode('utf-8')
	default.exposed = True

class LegacyPage():
	"""
	Forwards legacy /day/{channel}/{date}#{time}.{nick} in local time to
	the proper page at /day (in UTC).
	"""
	timezone = pytz.timezone('US/Pacific')

	@cherrypy.expose
	def default(self, channel, date_s):
		"""
		Return a web page that will get the fragment out and pass it to
		us so we can parse it.
		"""
		return textwrap.dedent("""

		""").lstrip()

	@cherrypy.expose
	def forward(self, channel, date_s, fragment):
		"""
		Given an HREF in the legacy timezone, redirect to an href for UTC.
		"""
		time_s, sep, nick = fragment.rpartition('.')
		time = datetime.datetime.strptime(time_s, '%H.%M.%S')
		date = datetime.datetime.strptime(date_s, '%Y-%m-%d')
		dt = datetime.datetime.combine(date, time.time())
		loc_dt = self.timezone.localize(dt)
		utc_dt = loc_dt.astimezone(pytz.utc)
		url_fmt = '/day/{channel}/{target_date}#{target_time}.{nick}'
		raise cherrypy.HTTPRedirect(
			url_fmt.format(
				target_date = utc_dt.date().isoformat(),
				target_time = utc_dt.time().strftime('%H.%M.%S'),
				**vars()
				),
			301,
		)

class PmxbotPages(object):
	channel = ChannelPage()
	day = DayPage()
	karma = KarmaPage()
	search = SearchPage()
	help = HelpPage()
	legacy = LegacyPage()

	def default(self):
		page = jenv.get_template('index.html')
		db = log_db()
		context = get_context()
		chans = []
		for chan in sorted(db.list_channels(), key = string.lower):
			last = db.last_message(chan)
			summary = [
				chan,
				last['datetime'].strftime("%Y-%m-%d %H:%M"),
				last['datetime'].date(),
				last['datetime'].time(),
				last['nick'],
				cgi.escape(last['message'][:75]),
				make_anchor([last['datetime'].time(), last['nick']]),
			]
			chans.append(summary)
		context['chans'] = chans
		return page.render(**context).encode('utf-8')
	default.exposed = True

def patch_compat(config):
	"""
	Support older config values.
	"""
	if 'web_host' in config:
		config['host'] = config.pop('web_host')
	if 'web_port' in config:
		config['port'] = config.pop('web_port')

def startup(config):
	patch_compat(config)

	pmxbot.web.config.update(config)
	config = pmxbot.web.config

	pmxbot.core._load_library_extensions()

	if not config.web_base.startswith('/'):
		config['web_base'] = '/' + config.web_base
	if config.web_base.endswith('/'):
		config['web_base'] = config.web_base.rstrip('/')
	if 'logo' not in config:
		config['logo'] = posixpath.join(config.web_base, 'pmxbot.png')

	# Cherrypy configuration here
	app_conf = {
		'global': {
			'server.socket_port': config.port,
			'server.socket_host': config.host,
			#'tools.encode.on': True,
			'tools.encode.encoding': 'utf-8',
		},
		'/pmxbot.png': {
			'tools.staticfile.on': True,
			'tools.staticfile.filename': pkg_resources.resource_filename(
				'pmxbot.web', 'templates/pmxbot.png'),
		},
		'botconf': {'config': config},
	}

	cherrypy.quickstart(PmxbotPages(), config.web_base, config=app_conf)

def run():
	startup(pmxbot.core.get_args().config)

if __name__ == '__main__':
	run()
<|endoftext|>


<commit_before>from datetime import datetime

from django.db import models

STATUS = (('NT', 'untested'),
          ('OK', 'success'),
          ('NOK', 'failed'),
          ('DN', 'decicion needed'))

class Application(models.Model):
    name = models.CharField(max_length=50)
    active = models.BooleanField(default=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['name']

    @property
    def active_sections(self):
        return self.testsection_set.filter(active=True)

class TestSection(models.Model):
    name = models.CharField(max_length=50)
    app = models.ForeignKey(Application)
    active = models.BooleanField(default=True)

    def __str__(self):
        return '%s - %s' % (self.app, self.name)

    @property
    def active_testcases(self):
        return self.testcase_set.filter(active=True)

    class Meta:
        unique_together = ('name', 'app')

class TestCase(models.Model):
    number = models.PositiveIntegerField(null=True, blank=True)
    name = models.CharField(max_length=50)
    section = models.ForeignKey(TestSection)
    active = models.BooleanField(default=True)
    description = models.TextField(null=True, blank=True)
    preconditions = models.ManyToManyField('self')
    precondition_comment = models.TextField(null=True, blank=True)
    action = models.TextField(null=True, blank=True)
    expected = models.TextField(null=True, blank=True)

    def __str__(self):
        return '%s - %s' % (self.section, self.name)

    class Meta:
        unique_together = ('number', 'section')

class TestRun(models.Model):
    name = models.CharField(max_length=50)
    comment = models.TextField(null=True, blank=True)
    version = models.CharField(max_length=15)
    date = models.DateTimeField(default=datetime.now, blank=True)
    testcases = models.ManyToManyField(TestCase, through='TestCaseRun')

    def __str__(self):
        return '[%s] %s' % (self.date.strftime('%Y-%m-%d %H:%M'), self.name)

    class Meta:
        get_latest_by = 'date'
        ordering = ['-date']

    @property
    def is_finished(self):
        return self.testcaserun_set.exclude(status='NT').count() == 0

    @property
    def is_success(self):
        return self.testcaserun_set.filter(status='NOK').count() == 0

class TestCaseRun(models.Model):
    testcase = models.ForeignKey(TestCase)
    testrun = models.ForeignKey(TestRun)
    status = models.CharField(max_length=3, choices=STATUS, default='NT')
    comment = models.TextField(null=True, blank=True)

    def __str__(self):
        return '%s: %s' % (self.testrun, self.testcase)
<commit_msg>fix is_finished<commit_after>from datetime import datetime

from django.db import models

STATUS = (('NT', 'untested'),
          ('OK', 'success'),
          ('NOK', 'failed'),
          ('DN', 'decicion needed'))

class Application(models.Model):
    name = models.CharField(max_length=50)
    active = models.BooleanField(default=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['name']

    @property
    def active_sections(self):
        return self.testsection_set.filter(active=True)

class TestSection(models.Model):
    name = models.CharField(max_length=50)
    app = models.ForeignKey(Application)
    active = models.BooleanField(default=True)

    def __str__(self):
        return '%s - %s' % (self.app, self.name)

    @property
    def active_testcases(self):
        return self.testcase_set.filter(active=True)

    class Meta:
        unique_together = ('name', 'app')

class TestCase(models.Model):
    number = models.PositiveIntegerField(null=True, blank=True)
    name = models.CharField(max_length=50)
    section = models.ForeignKey(TestSection)
    active = models.BooleanField(default=True)
    description = models.TextField(null=True, blank=True)
    preconditions = models.ManyToManyField('self')
    precondition_comment = models.TextField(null=True, blank=True)
    action = models.TextField(null=True, blank=True)
    expected = models.TextField(null=True, blank=True)

    def __str__(self):
        return '%s - %s' % (self.section, self.name)

    class Meta:
        unique_together = ('number', 'section')

class TestRun(models.Model):
    name = models.CharField(max_length=50)
    comment = models.TextField(null=True, blank=True)
    version = models.CharField(max_length=15)
    date = models.DateTimeField(default=datetime.now, blank=True)
    testcases = models.ManyToManyField(TestCase, through='TestCaseRun')

    def __str__(self):
        return '[%s] %s' % (self.date.strftime('%Y-%m-%d %H:%M'), self.name)

    class Meta:
        get_latest_by = 'date'
        ordering = ['-date']

    @property
    def is_finished(self):
        return self.testcaserun_set.filter(status='NT').count() == 0

    @property
    def is_success(self):
        return self.testcaserun_set.filter(status='NOK').count() == 0

class TestCaseRun(models.Model):
    testcase = models.ForeignKey(TestCase)
    testrun = models.ForeignKey(TestRun)
    status = models.CharField(max_length=3, choices=STATUS, default='NT')
    comment = models.TextField(null=True, blank=True)

    def __str__(self):
        return '%s: %s' % (self.testrun, self.testcase)
<|endoftext|>


<commit_before>#!/usr/bin/env python
# coding: utf-8

import xmltodict
from StringIO import StringIO
from lxml import etree

class CannotValidate(Exception):
    pass

class Schema(object):

    def __init__(self, xsd_input=None):
        """
        Parse the XSD
        """
        if xsd_input is not None:
            self.xsd_doc = etree.parse(self._handle_xml(xsd_input))
            self.xmlschema = etree.XMLSchema(self.xsd_doc)

    def _handle_errors(self, errors_list):
        """
        Handles errors list

        Output Format:
        [(DOMIAN, LINE, COLUMN, LEVEL, TYPE_NAME, MESSAGE),]

        Ex.: [(PARSER, 3, 51, FATAL, ERR_TAG_NAME_MISMATCH, Opening and
            ending tag mismatch: statpage line 3 and startpage),
            (SCHEMASV, 2, 0, ERROR, SCHEMAV_CVC_ELT_1, Element 'wizard':
            No matching global declaration available for the validation root)]
        """
        errors = []
        for error in errors_list:
            errors.append((error.domain_name, error.line, error.column,
                error.level_name, error.type_name, error.message))
        return errors

    def _handle_xml(self, xml_input):
        """
        Handles the type of xml_input
        """
        if hasattr(xml_input, 'read'):
            return xml_input
        else:
            return StringIO(xml_input)

    def get_validation_errors(self, xml_input):
        """
        This method returns a list of validation errors. If there are no errors
        an empty list is returned
        """
        errors = []
        try:
            parsed_xml = etree.parse(self._handle_xml(xml_input))
            self.xmlschema.assertValid(parsed_xml)
        except (etree.DocumentInvalid, etree.XMLSyntaxError), e:
            errors = self._handle_errors(e.error_log)
        except AttributeError:
            raise CannotValidate('Set XSD to validate the XML')
        return errors

    def validate(self, xml_input):
        """
        This method validate the parsing and schema, return a boolean
        """
        parsed_xml = etree.parse(self._handle_xml(xml_input))
        try:
            return self.xmlschema.validate(parsed_xml)
        except AttributeError:
            raise CannotValidate('Set XSD to validate the XML')

    def deserialize(self, xml_input, *args, **kwargs):
        """
        Convert XML to dict object
        """
        return xmltodict.parse(xml_input, *args, **kwargs)

    def serialize(self, dict_input, **kwargs):
        """
        Convert dict to XML
        """
        return xmltodict.unparse(dict_input, **kwargs)
<commit_msg>Ajuste na assinatura do método serialize<commit_after>#!/usr/bin/env python
# coding: utf-8

import xmltodict
from StringIO import StringIO
from lxml import etree

class CannotValidate(Exception):
    pass

class Schema(object):

    def __init__(self, xsd_input=None):
        """
        Parse the XSD
        """
        if xsd_input is not None:
            self.xsd_doc = etree.parse(self._handle_xml(xsd_input))
            self.xmlschema = etree.XMLSchema(self.xsd_doc)

    def _handle_errors(self, errors_list):
        """
        Handles errors list

        Output Format:
        [(DOMIAN, LINE, COLUMN, LEVEL, TYPE_NAME, MESSAGE),]

        Ex.: [(PARSER, 3, 51, FATAL, ERR_TAG_NAME_MISMATCH, Opening and
            ending tag mismatch: statpage line 3 and startpage),
            (SCHEMASV, 2, 0, ERROR, SCHEMAV_CVC_ELT_1, Element 'wizard':
            No matching global declaration available for the validation root)]
        """
        errors = []
        for error in errors_list:
            errors.append((error.domain_name, error.line, error.column,
                error.level_name, error.type_name, error.message))
        return errors

    def _handle_xml(self, xml_input):
        """
        Handles the type of xml_input
        """
        if hasattr(xml_input, 'read'):
            return xml_input
        else:
            return StringIO(xml_input)

    def get_validation_errors(self, xml_input):
        """
        This method returns a list of validation errors. If there are no errors
        an empty list is returned
        """
        errors = []
        try:
            parsed_xml = etree.parse(self._handle_xml(xml_input))
            self.xmlschema.assertValid(parsed_xml)
        except (etree.DocumentInvalid, etree.XMLSyntaxError), e:
            errors = self._handle_errors(e.error_log)
        except AttributeError:
            raise CannotValidate('Set XSD to validate the XML')
        return errors

    def validate(self, xml_input):
        """
        This method validate the parsing and schema, return a boolean
        """
        parsed_xml = etree.parse(self._handle_xml(xml_input))
        try:
            return self.xmlschema.validate(parsed_xml)
        except AttributeError:
            raise CannotValidate('Set XSD to validate the XML')

    def deserialize(self, xml_input, *args, **kwargs):
        """
        Convert XML to dict object
        """
        return xmltodict.parse(xml_input, *args, **kwargs)

    def serialize(self, dict_input, *args, **kwargs):
        """
        Convert dict to XML
        """
        return xmltodict.unparse(dict_input, **kwargs)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.db import models
from django.contrib import admin
from django.conf import settings
from django.forms.widgets import TextInput
from django.utils import six
from django.utils.text import Truncator
from django.utils.translation import ugettext_lazy as _

from .fields import CommaSeparatedEmailField
from .models import Attachment, Log, Email, EmailTemplate, STATUS

def get_message_preview(instance):
    return (u'{0}...'.format(instance.message[:25]) if len(instance.message) > 25
            else instance.message)

get_message_preview.short_description = 'Message'

class LogInline(admin.StackedInline):
    model = Log
    extra = 0

class CommaSeparatedEmailWidget(TextInput):

    def __init__(self, *args, **kwargs):
        super(CommaSeparatedEmailWidget, self).__init__(*args, **kwargs)
        self.attrs.update({'class': 'vTextField'})

    def _format_value(self, value):
        # If the value is a string wrap it in a list so it does not get sliced.
        if isinstance(value, six.string_types):
            value = [value, ]
        return ','.join([item for item in value])

def requeue(modeladmin, request, queryset):
    """An admin action to requeue emails."""
    queryset.update(status=STATUS.queued)

requeue.short_description = 'Requeue selected emails'

class EmailAdmin(admin.ModelAdmin):
    list_display = ('id', 'to_display', 'subject', 'template',
                    'status', 'last_updated')
    inlines = [LogInline]
    list_filter = ['status']
    formfield_overrides = {
        CommaSeparatedEmailField: {'widget': CommaSeparatedEmailWidget}
    }
    actions = [requeue]

    def get_queryset(self, request):
        return super(EmailAdmin, self).get_queryset(request).select_related('template')

    def to_display(self, instance):
        return ', '.join(instance.to)

    to_display.short_description = 'to'
    to_display.admin_order_field = 'to'

class LogAdmin(admin.ModelAdmin):
    list_display = ('date', 'email', 'status', get_message_preview)

class SubjectField(TextInput):
    def __init__(self, *args, **kwargs):
        super(SubjectField, self).__init__(*args, **kwargs)
        self.attrs.update({'style': 'width: 610px;'})

class EmailTemplateAdminForm(forms.ModelForm):

    language = forms.ChoiceField(choices=settings.LANGUAGES, required=False,
                                 help_text=_("Render template in alternative language"),
                                 label=_("Language"))

    class Meta:
        model = EmailTemplate
        fields = ('name', 'description', 'subject',
                  'content', 'html_content', 'language', 'default_template')

class EmailTemplateInline(admin.StackedInline):
    form = EmailTemplateAdminForm
    model = EmailTemplate
    extra = 0
    fields = ('language', 'subject', 'content', 'html_content',)
    formfield_overrides = {
        models.CharField: {'widget': SubjectField}
    }

    def get_max_num(self, request, obj=None, **kwargs):
        return len(settings.LANGUAGES)

class EmailTemplateAdmin(admin.ModelAdmin):
    form = EmailTemplateAdminForm
    list_display = ('name', 'description_shortened', 'subject', 'languages_compact', 'created')
    search_fields = ('name', 'description', 'subject')
    fieldsets = [
        (None, {
            'fields': ('name', 'description'),
        }),
        (_("Default Content"), {
            'fields': ('subject', 'content', 'html_content'),
        }),
    ]
    inlines = (EmailTemplateInline,) if settings.USE_I18N else ()
    formfield_overrides = {
        models.CharField: {'widget': SubjectField}
    }

    def get_queryset(self, request):
        return self.model.objects.filter(default_template__isnull=True)

    def description_shortened(self, instance):
        return Truncator(instance.description.split('\n')[0]).chars(200)
    description_shortened.short_description = _("Description")
    description_shortened.admin_order_field = 'description'

    def languages_compact(self, instance):
        languages = [tt.language for tt in instance.translated_templates.all()]
        return ', '.join(languages)
    languages_compact.short_description = _("Languages")

class AttachmentAdmin(admin.ModelAdmin):
    list_display = ('name', 'file', )

admin.site.register(Email, EmailAdmin)
admin.site.register(Log, LogAdmin)
admin.site.register(EmailTemplate, EmailTemplateAdmin)
admin.site.register(Attachment, AttachmentAdmin)
<commit_msg>Fix #193 TypeError at /admin/post_office/email/add/<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.db import models
from django.contrib import admin
from django.conf import settings
from django.forms.widgets import TextInput
from django.utils import six
from django.utils.text import Truncator
from django.utils.translation import ugettext_lazy as _

from .fields import CommaSeparatedEmailField
from .models import Attachment, Log, Email, EmailTemplate, STATUS

def get_message_preview(instance):
    return (u'{0}...'.format(instance.message[:25]) if len(instance.message) > 25
            else instance.message)

get_message_preview.short_description = 'Message'

class LogInline(admin.StackedInline):
    model = Log
    extra = 0

class CommaSeparatedEmailWidget(TextInput):

    def __init__(self, *args, **kwargs):
        super(CommaSeparatedEmailWidget, self).__init__(*args, **kwargs)
        self.attrs.update({'class': 'vTextField'})

    def _format_value(self, value):
        # If the value is a string wrap it in a list so it does not get sliced.
        if not value:
            return ''
        if isinstance(value, six.string_types):
            value = [value, ]
        return ','.join([item for item in value])

def requeue(modeladmin, request, queryset):
    """An admin action to requeue emails."""
    queryset.update(status=STATUS.queued)

requeue.short_description = 'Requeue selected emails'

class EmailAdmin(admin.ModelAdmin):
    list_display = ('id', 'to_display', 'subject', 'template',
                    'status', 'last_updated')
    inlines = [LogInline]
    list_filter = ['status']
    formfield_overrides = {
        CommaSeparatedEmailField: {'widget': CommaSeparatedEmailWidget}
    }
    actions = [requeue]

    def get_queryset(self, request):
        return super(EmailAdmin, self).get_queryset(request).select_related('template')

    def to_display(self, instance):
        return ', '.join(instance.to)

    to_display.short_description = 'to'
    to_display.admin_order_field = 'to'

class LogAdmin(admin.ModelAdmin):
    list_display = ('date', 'email', 'status', get_message_preview)

class SubjectField(TextInput):
    def __init__(self, *args, **kwargs):
        super(SubjectField, self).__init__(*args, **kwargs)
        self.attrs.update({'style': 'width: 610px;'})

class EmailTemplateAdminForm(forms.ModelForm):

    language = forms.ChoiceField(choices=settings.LANGUAGES, required=False,
                                 help_text=_("Render template in alternative language"),
                                 label=_("Language"))

    class Meta:
        model = EmailTemplate
        fields = ('name', 'description', 'subject',
                  'content', 'html_content', 'language', 'default_template')

class EmailTemplateInline(admin.StackedInline):
    form = EmailTemplateAdminForm
    model = EmailTemplate
    extra = 0
    fields = ('language', 'subject', 'content', 'html_content',)
    formfield_overrides = {
        models.CharField: {'widget': SubjectField}
    }

    def get_max_num(self, request, obj=None, **kwargs):
        return len(settings.LANGUAGES)

class EmailTemplateAdmin(admin.ModelAdmin):
    form = EmailTemplateAdminForm
    list_display = ('name', 'description_shortened', 'subject', 'languages_compact', 'created')
    search_fields = ('name', 'description', 'subject')
    fieldsets = [
        (None, {
            'fields': ('name', 'description'),
        }),
        (_("Default Content"), {
            'fields': ('subject', 'content', 'html_content'),
        }),
    ]
    inlines = (EmailTemplateInline,) if settings.USE_I18N else ()
    formfield_overrides = {
        models.CharField: {'widget': SubjectField}
    }

    def get_queryset(self, request):
        return self.model.objects.filter(default_template__isnull=True)

    def description_shortened(self, instance):
        return Truncator(instance.description.split('\n')[0]).chars(200)
    description_shortened.short_description = _("Description")
    description_shortened.admin_order_field = 'description'

    def languages_compact(self, instance):
        languages = [tt.language for tt in instance.translated_templates.all()]
        return ', '.join(languages)
    languages_compact.short_description = _("Languages")

class AttachmentAdmin(admin.ModelAdmin):
    list_display = ('name', 'file', )

admin.site.register(Email, EmailAdmin)
admin.site.register(Log, LogAdmin)
admin.site.register(EmailTemplate, EmailTemplateAdmin)
admin.site.register(Attachment, AttachmentAdmin)
<|endoftext|>


<commit_before>"""Provide the Front class."""
from ..const import API_PATH
from .listing.generator import ListingGenerator
from .base import PRAWBase

class Inbox(PRAWBase):
    """Inbox is a Listing class that represents the Inbox."""

    def all(self, **generator_kwargs):
        """Return a ListingGenerator for all inbox comments and messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        """
        return ListingGenerator(self._reddit, API_PATH['inbox'],
                                **generator_kwargs)

    def comment_replies(self, **generator_kwargs):
        """Return a ListingGenerator for comment replies.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        """
        return ListingGenerator(self._reddit, API_PATH['comment_replies'],
                                **generator_kwargs)

    def mark_read(self, items):
        """Mark Comments or Messages as read.

        :param items: A list containing Comments and Messages to be be marked
            as read relative to the authenticated user's inbox.

        Requests are batched at 25 items (reddit limit).

        """
        while items:
            data = {'id': ','.join(x.fullname for x in items[:25])}
            self._reddit.post(API_PATH['read_message'], data=data)
            items = items[25:]

    def mark_unread(self, items):
        """Unmark Comments or Messages as read.

        :param items: A list containing Comments and Messages to be be marked
            as unread relative to the authenticated user's inbox.

        Requests are batched at 25 items (reddit limit).

        """
        while items:
            data = {'id': ','.join(x.fullname for x in items[:25])}
            self._reddit.post(API_PATH['unread_message'], data=data)
            items = items[25:]

    def mentions(self, **generator_kwargs):
        """Return a ListingGenerator for mentions.

        A mention is :class:`.Comment` in which the authorized redditor is
        named in its body like ``/u/redditor_name``.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        """
        return ListingGenerator(self._reddit, API_PATH['mentions'],
                                **generator_kwargs)

    def message(self, message_id):
        """Return a Message corresponding to ``message_id``.

        :param message_id: The base36 id of a message.

        """
        listing = self._reddit.get(API_PATH['message'].format(id=message_id))
        messages = [listing[0]] + list(self._reddit._objector
                                       .objectify(listing[0].replies))
        while messages:
            message = messages.pop(0)
            if message.id == message_id:
                return message

    def messages(self, **generator_kwargs):
        """Return a ListingGenerator for inbox messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        """
        return ListingGenerator(self._reddit, API_PATH['messages'],
                                **generator_kwargs)

    def submission_replies(self, **generator_kwargs):
        """Return a ListingGenerator for submission replies.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        """
        return ListingGenerator(self._reddit, API_PATH['submission_replies'],
                                **generator_kwargs)

    def sent(self, **generator_kwargs):
        """Return a ListingGenerator for sent messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        """
        return ListingGenerator(self._reddit, API_PATH['sent'],
                                **generator_kwargs)

    def unread(self, mark_read=False, **generator_kwargs):
        """Return a ListingGenerator for unread comments and messages.

        :param mark_read: Marks the messages as read when they're obtained
            (default: False).

        .. note:: When marking messages as read, the entire batch (up to 100 at
                  a time) is marked as read when fetched. Failure to consume
                  the entire listing may result in missed messages if you only
                  obtain unread messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        """
        params = {'mark': mark_read}
        return ListingGenerator(self._reddit, API_PATH['unread'],
                                params=params, **generator_kwargs)
<commit_msg>Add documentation examples to reddit.inbox<commit_after>"""Provide the Front class."""
from ..const import API_PATH
from .listing.generator import ListingGenerator
from .base import PRAWBase

class Inbox(PRAWBase):
    """Inbox is a Listing class that represents the Inbox."""

    def all(self, **generator_kwargs):
        """Return a ListingGenerator for all inbox comments and messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        To output the type and ID of all items available via this listing do:

        .. code:: python

           for item in reddit.inbox.all(limit=None):
               print(repr(item))

        """
        return ListingGenerator(self._reddit, API_PATH['inbox'],
                                **generator_kwargs)

    def comment_replies(self, **generator_kwargs):
        """Return a ListingGenerator for comment replies.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        To output the author of one request worth of comment replies try:

        .. code:: python

           for reply in reddit.inbox.comment_replies():
               print(reply.author)

        """
        return ListingGenerator(self._reddit, API_PATH['comment_replies'],
                                **generator_kwargs)

    def mark_read(self, items):
        """Mark Comments or Messages as read.

        :param items: A list containing instances of :class:`.Comment` and/or
            :class:`.Message` to be be marked as read relative to the
            authorized user's inbox.

        Requests are batched at 25 items (reddit limit).

        For example, to mark all unread Messages as read, try:

        .. code:: python

            from praw.models import Message
            unread_messages = []
            for item in reddit.inbox.unread(limit=None):
                if isinstance(item, Message):
                    unread_messages.append(item)
            reddit.inbox.mark_read(unread_messages)

        .. seealso::

           :meth:`.Comment.mark_read` and :meth:`.Message.mark_read`

        """
        while items:
            data = {'id': ','.join(x.fullname for x in items[:25])}
            self._reddit.post(API_PATH['read_message'], data=data)
            items = items[25:]

    def mark_unread(self, items):
        """Unmark Comments or Messages as read.

        :param items: A list containing instances of :class:`.Comment` and/or
            :class:`.Message` to be be marked as unread relative to the
            authorized user's inbox.

        Requests are batched at 25 items (reddit limit).

        For example, to mark the first 10 items as unread try:

        .. code:: python

            to_unread = list(reddit.inbox.all(limit=10))
            reddit.inbox.mark_unread(to_unread)

        .. seealso::

           :meth:`.Comment.mark_unread` and :meth:`.Message.mark_unread`

        """
        while items:
            data = {'id': ','.join(x.fullname for x in items[:25])}
            self._reddit.post(API_PATH['unread_message'], data=data)
            items = items[25:]

    def mentions(self, **generator_kwargs):
        r"""Return a ListingGenerator for mentions.

        A mention is :class:`.Comment` in which the authorized redditor is
        named in its body like ``/u/redditor_name``.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        For example, to output the author and body of the first 25 mentions
        try:

        .. code:: python

           for mention in reddit.inbox.mentions(limit=25):
               print('{}\n{}\n'.format(mention.author, mention.body))

        """
        return ListingGenerator(self._reddit, API_PATH['mentions'],
                                **generator_kwargs)

    def message(self, message_id):
        """Return a Message corresponding to ``message_id``.

        :param message_id: The base36 id of a message.

        Example:

        .. code:: python

           message = reddit.inbox.message('7bnlgu')

        """
        listing = self._reddit.get(API_PATH['message'].format(id=message_id))
        messages = [listing[0]] + list(self._reddit._objector
                                       .objectify(listing[0].replies))
        while messages:
            message = messages.pop(0)
            if message.id == message_id:
                return message

    def messages(self, **generator_kwargs):
        """Return a ListingGenerator for inbox messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        For example, to output the subject of the most recent 5 messages try:

        .. code:: python

           for message in reddit.inbox.messages(limit=5):
               print(message.subject)

        """
        return ListingGenerator(self._reddit, API_PATH['messages'],
                                **generator_kwargs)

    def sent(self, **generator_kwargs):
        """Return a ListingGenerator for sent messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        For example, to output the receipient of the mot recent 15 messages
        try:

        .. code:: python

           for message in reddit.inbox.sent(limit=15):
               print(message.dest)

        """
        return ListingGenerator(self._reddit, API_PATH['sent'],
                                **generator_kwargs)

    def submission_replies(self, **generator_kwargs):
        """Return a ListingGenerator for submission replies.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        To output the author of one request worth of submission replies try:

        .. code:: python

           for reply in reddit.inbox.submission_replies():
               print(reply.author)

        """
        return ListingGenerator(self._reddit, API_PATH['submission_replies'],
                                **generator_kwargs)

    def unread(self, mark_read=False, **generator_kwargs):
        """Return a ListingGenerator for unread comments and messages.

        :param mark_read: Marks the messages as read when they're obtained
            (default: False).

        .. note:: When marking messages as read, the entire batch (up to 100 at
                  a time) is marked as read when fetched. Failure to consume
                  the entire listing may result in missed messages if you only
                  obtain unread messages.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        For example, to output the author of unread comments try:

        .. code:: python

           from praw.models import Comment
           for item in reddit.inbox.unread(limit=None):
               if isinstance(item, Comment):
                   print(item.author)

        """
        params = {'mark': mark_read}
        return ListingGenerator(self._reddit, API_PATH['unread'],
                                params=params, **generator_kwargs)
<|endoftext|>


<commit_before>import json
import random
import datetime
from django.conf import settings
try:
    from django.urls import reverse
except ImportError:
    from django.core.urlresolvers import reverse
from django.core.validators import RegexValidator
from django.db import models, IntegrityError
from django.db import transaction
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _
from . import settings as purl_settings

class PrivateUrlManager(models.Manager):
    def get_or_none(self, action, token):
        try:
            return self.select_related('user').get(action=action, token=token)
        except self.model.DoesNotExist:
            return None

class PrivateUrl(models.Model):
    TOKEN_MIN_SIZE = 8
    TOKEN_MAX_SIZE = 64

    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), null=True, blank=True,
                             on_delete=models.CASCADE)
    action = models.SlugField(verbose_name=_('action'), max_length=40, db_index=True,
                              validators=[RegexValidator(r'^[-_a-zA-Z0-9]+$')])
    token = models.SlugField(verbose_name=_('token'), max_length=TOKEN_MAX_SIZE,
                             validators=[RegexValidator(r'^[-a-zA-Z0-9]+$')])
    expire = models.DateTimeField(verbose_name=_('expire'), null=True, blank=True, db_index=True)
    data = models.TextField(verbose_name=_('data'), blank=True)
    created = models.DateTimeField(verbose_name=_('created'), auto_now_add=True, db_index=True)
    hits_limit = models.PositiveIntegerField(verbose_name=_('hits limit'), default=1,
                                             help_text=_('Set 0 to unlimited.'))
    hit_counter = models.PositiveIntegerField(verbose_name=_('hit counter'), default=0)
    first_hit = models.DateTimeField(verbose_name=_('first hit'), null=True, blank=True)
    last_hit = models.DateTimeField(verbose_name=_('last hit'), null=True, blank=True)
    auto_delete = models.BooleanField(verbose_name=_('auto delete'), default=False,
                                      help_text=_('Delete object if it can no longer be used.'))

    objects = PrivateUrlManager()

    class Meta:
        db_table = 'privateurl_privateurl'
        ordering = ('-created',)
        unique_together = ('action', 'token')
        verbose_name = _('private url')
        verbose_name_plural = _('private urls')

    def get_data(self):
        if self.data == '':
            return None
        return json.loads(self.data)

    def set_data(self, data):
        if data is None:
            self.data = ''
        else:
            self.data = json.dumps(data, sort_keys=True)

    @classmethod
    def create(cls, action, user=None, expire=None, data=None, hits_limit=1, auto_delete=False,
               token_size=None, replace=False, dashed_piece_size=None):
        """
        Create new object PrivateUrl.
        action - name of action (slug)
        user - user object or None
        expire - expire time, datetime, timedelta or None for disable time limit
        data - additional data that, any object that cant dumps as standard json
        hits_limit - limit of request hits, int (0 - ulimited)
        auto_delete - auto remove when url will be not available, bool
        token_size - length of token, tuple (min, max) or static int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_SIZE
        replace - remove exist object for user and action before creating, bool
        dashed_piece_size - split token with dash every N symbols, int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_DASHED_PIECE_SIZE
        """
        if replace and user:
            cls.objects.filter(action=action, user=user).delete()
        if isinstance(expire, datetime.timedelta):
            expire = timezone.now() + expire
        max_tries, n = 20, 0
        while True:
            try:
                token = cls.generate_token(size=token_size, dashed_piece_size=dashed_piece_size)
                obj = PrivateUrl(user=user, action=action, token=token, expire=expire,
                                 hits_limit=hits_limit, auto_delete=auto_delete)
                obj.set_data(data)
                with transaction.atomic():
                    obj.save()
                return obj
            except IntegrityError:
                n += 1
                if n > max_tries:
                    raise RuntimeError("Failed to create PrivateUrl object (action={}, token_size={})".format(
                        action, token_size
                    ))

    def is_available(self, dt=None):
        """
        Return True if object can be used.
        """
        if self.expire and self.expire <= (dt or timezone.now()):
            return False
        if self.hits_limit and self.hits_limit <= self.hit_counter:
            return False
        return True

    def hit_counter_inc(self):
        obj_is_exists = self.pk is not None
        now = timezone.now()
        self.hit_counter += 1
        if self.auto_delete and not self.is_available(dt=now):
            if obj_is_exists:
                self.delete()
            return
        uf = {'hit_counter', 'last_hit'}
        if not self.first_hit:
            self.first_hit = now
            uf.add('first_hit')
        self.last_hit = now
        if obj_is_exists:
            self.save(update_fields=uf)

    @classmethod
    def generate_token(cls, size=None, dashed_piece_size=None):
        """
        Generate new unique token.
        size - length of token, tuple (min, max) or static int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_SIZE
        dashed_piece_size - split token with dash every N symbols, int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_DASHED_PIECE_SIZE
        """
        if size is None:
            size = purl_settings.PRIVATEURL_DEFAULT_TOKEN_SIZE

        if dashed_piece_size is None:
            dashed_piece_size = purl_settings.PRIVATEURL_DEFAULT_TOKEN_DASHED_PIECE_SIZE

        if not isinstance(size, (int, list, tuple)):
            raise AttributeError('Attr size must be int, list or tuple.')

        size = (size, size) if isinstance(size, int) else tuple(size)

        if len(size) != 2:
            raise AttributeError('Attr size must contains two values.')

        for v in size:
            if not isinstance(v, int) or not (cls.TOKEN_MIN_SIZE <= v <= cls.TOKEN_MAX_SIZE):
                raise AttributeError('Attr size must contains values between {} and {}.'.format(
                    cls.TOKEN_MIN_SIZE, cls.TOKEN_MAX_SIZE
                ))

        if size[0] > size[1]:
            raise AttributeError('Attr size has incorrect values: first value must be less than second one.')

        if not isinstance(dashed_piece_size, int):
            raise AttributeError('Attr dash_split_each must be int.')
        elif dashed_piece_size < 0:
            raise AttributeError('Attr dash_split_each must be greater or equal 0.')

        if size[0] != size[1]:
            random.seed(get_random_string(length=100))
            _size = random.randint(*size)
        else:
            _size = size[0]

        token = get_random_string(length=_size)
        if dashed_piece_size:
            n = dashed_piece_size
            while n < len(token):
                token = token[:n] + '-' + token[n:]
                n += dashed_piece_size + 1
            token = token[:_size].rstrip('-')

        return token

    def get_absolute_url(self):
        return reverse('{}:privateurl'.format(purl_settings.PRIVATEURL_URL_NAMESPACE),
                       kwargs={'action': self.action, 'token': self.token})
<commit_msg>Some fix.<commit_after>import json
import random
import datetime
from django.conf import settings
try:
    from django.urls import reverse
except ImportError:
    from django.core.urlresolvers import reverse
from django.core.validators import RegexValidator
from django.db import models, IntegrityError
from django.db import transaction
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _
from . import settings as purl_settings

class PrivateUrlManager(models.Manager):
    def get_or_none(self, action, token):
        try:
            return self.select_related('user').get(action=action, token=token)
        except self.model.DoesNotExist:
            pass

class PrivateUrl(models.Model):
    TOKEN_MIN_SIZE = 8
    TOKEN_MAX_SIZE = 64

    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), null=True, blank=True,
                             on_delete=models.CASCADE)
    action = models.SlugField(verbose_name=_('action'), max_length=40, db_index=True,
                              validators=[RegexValidator(r'^[-_a-zA-Z0-9]+$')])
    token = models.SlugField(verbose_name=_('token'), max_length=TOKEN_MAX_SIZE,
                             validators=[RegexValidator(r'^[-a-zA-Z0-9]+$')])
    expire = models.DateTimeField(verbose_name=_('expire'), null=True, blank=True, db_index=True)
    data = models.TextField(verbose_name=_('data'), blank=True)
    created = models.DateTimeField(verbose_name=_('created'), auto_now_add=True, db_index=True)
    hits_limit = models.PositiveIntegerField(verbose_name=_('hits limit'), default=1,
                                             help_text=_('Set 0 to unlimited.'))
    hit_counter = models.PositiveIntegerField(verbose_name=_('hit counter'), default=0)
    first_hit = models.DateTimeField(verbose_name=_('first hit'), null=True, blank=True)
    last_hit = models.DateTimeField(verbose_name=_('last hit'), null=True, blank=True)
    auto_delete = models.BooleanField(verbose_name=_('auto delete'), default=False,
                                      help_text=_('Delete object if it can no longer be used.'))

    objects = PrivateUrlManager()

    class Meta:
        db_table = 'privateurl_privateurl'
        ordering = ('-created',)
        unique_together = ('action', 'token')
        verbose_name = _('private url')
        verbose_name_plural = _('private urls')

    def get_data(self):
        if self.data != '':
            return json.loads(self.data)

    def set_data(self, data):
        if data is None:
            self.data = ''
        else:
            self.data = json.dumps(data, sort_keys=True)

    @classmethod
    def create(cls, action, user=None, expire=None, data=None, hits_limit=1, auto_delete=False,
               token_size=None, replace=False, dashed_piece_size=None):
        """
        Create new object PrivateUrl.
        action - name of action (slug)
        user - user object or None
        expire - expire time, datetime, timedelta or None for disable time limit
        data - additional data that, any object that cant dumps as standard json
        hits_limit - limit of request hits, int (0 - ulimited)
        auto_delete - auto remove when url will be not available, bool
        token_size - length of token, tuple (min, max) or static int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_SIZE
        replace - remove exist object for user and action before creating, bool
        dashed_piece_size - split token with dash every N symbols, int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_DASHED_PIECE_SIZE
        """
        if replace and user:
            cls.objects.filter(action=action, user=user).delete()
        if isinstance(expire, datetime.timedelta):
            expire = timezone.now() + expire
        max_tries, n = 20, 0
        while True:
            try:
                token = cls.generate_token(size=token_size, dashed_piece_size=dashed_piece_size)
                obj = PrivateUrl(user=user, action=action, token=token, expire=expire,
                                 hits_limit=hits_limit, auto_delete=auto_delete)
                obj.set_data(data)
                with transaction.atomic():
                    obj.save()
                return obj
            except IntegrityError:
                n += 1
                if n > max_tries:
                    raise RuntimeError("Failed to create PrivateUrl object (action={}, token_size={})".format(
                        action, token_size
                    ))

    def is_available(self, dt=None):
        """
        Return True if object can be used.
        """
        if self.expire and self.expire <= (dt or timezone.now()):
            return False
        if self.hits_limit and self.hits_limit <= self.hit_counter:
            return False
        return True

    def hit_counter_inc(self):
        obj_is_exists = self.pk is not None
        now = timezone.now()
        self.hit_counter += 1
        if self.auto_delete and not self.is_available(dt=now):
            if obj_is_exists:
                self.delete()
            return
        uf = {'hit_counter', 'last_hit'}
        if not self.first_hit:
            self.first_hit = now
            uf.add('first_hit')
        self.last_hit = now
        if obj_is_exists:
            self.save(update_fields=uf)

    @classmethod
    def generate_token(cls, size=None, dashed_piece_size=None):
        """
        Generate new unique token.
        size - length of token, tuple (min, max) or static int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_SIZE
        dashed_piece_size - split token with dash every N symbols, int,
            None set default value from settings.PRIVATEURL_DEFAULT_TOKEN_DASHED_PIECE_SIZE
        """
        if size is None:
            size = purl_settings.PRIVATEURL_DEFAULT_TOKEN_SIZE

        if dashed_piece_size is None:
            dashed_piece_size = purl_settings.PRIVATEURL_DEFAULT_TOKEN_DASHED_PIECE_SIZE

        if not isinstance(size, (int, list, tuple)):
            raise AttributeError('Attr size must be int, list or tuple.')

        size = (size, size) if isinstance(size, int) else tuple(size)

        if len(size) != 2:
            raise AttributeError('Attr size must contains two values.')

        for v in size:
            if not isinstance(v, int) or not (cls.TOKEN_MIN_SIZE <= v <= cls.TOKEN_MAX_SIZE):
                raise AttributeError('Attr size must contains values between {} and {}.'.format(
                    cls.TOKEN_MIN_SIZE, cls.TOKEN_MAX_SIZE
                ))

        if size[0] > size[1]:
            raise AttributeError('Attr size has incorrect values: first value must be less than second one.')

        if not isinstance(dashed_piece_size, int):
            raise AttributeError('Attr dash_split_each must be int.')
        elif dashed_piece_size < 0:
            raise AttributeError('Attr dash_split_each must be greater or equal 0.')

        if size[0] != size[1]:
            random.seed(get_random_string(length=100))
            _size = random.randint(*size)
        else:
            _size = size[0]

        token = get_random_string(length=_size)
        if dashed_piece_size:
            n = dashed_piece_size
            while n < len(token):
                token = token[:n] + '-' + token[n:]
                n += dashed_piece_size + 1
            token = token[:_size].rstrip('-')

        return token

    def get_absolute_url(self):
        return reverse('{}:privateurl'.format(purl_settings.PRIVATEURL_URL_NAMESPACE),
                       kwargs={'action': self.action, 'token': self.token})
<|endoftext|>