Compare commits
No commits in common. "master" and "v0.1a13" have entirely different histories.
79 changed files with 1214 additions and 1946 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
edbob.egg-info
|
190
CHANGES.txt
190
CHANGES.txt
|
@ -1,194 +1,4 @@
|
|||
|
||||
0.1.2
|
||||
-----
|
||||
|
||||
* Allow config file to prevent logging configuration from happening.
|
||||
|
||||
|
||||
0.1.1
|
||||
-----
|
||||
|
||||
* Some random things required in production at MaMa Jean's...
|
||||
|
||||
Specifically this is known to replace occurrences of e.g. ``edbob.User`` with
|
||||
a more standard (properly imported) reference to ``User``.
|
||||
|
||||
|
||||
0.1a29
|
||||
------
|
||||
|
||||
* Removed ``setup.cfg`` file.
|
||||
|
||||
* Changed some logging instances from ``INFO`` to ``DEBUG``.
|
||||
|
||||
* Updated ``repr()`` output for model classes.
|
||||
|
||||
|
||||
0.1a28
|
||||
------
|
||||
|
||||
- [bug] Secured daemon PID files. They are no longer readable or writeable
|
||||
(whoops) by anyone other than the user who owns the process.
|
||||
|
||||
- [feature] Added ``--progress`` argument to command system.
|
||||
|
||||
- [general] Added initial Fabric script.
|
||||
|
||||
|
||||
0.1a27
|
||||
------
|
||||
|
||||
- [feature] Overhauled file monitors. Added the "process existing" (defaults
|
||||
to true) and "stop on error" (defaults to false) features to both Linux and
|
||||
Win32 file monitors. Both features may be overridden in config. The Linux
|
||||
file monitor was rewritten as an ``edbob.daemon.Daemon`` class.
|
||||
|
||||
0.1a26
|
||||
------
|
||||
|
||||
- [feature] Added ``StrippingFieldRenderer``.
|
||||
|
||||
- [general] Some style tweaks.
|
||||
|
||||
- [bug] Fixed ``graft()`` function when called with module reference.
|
||||
|
||||
0.1a25
|
||||
------
|
||||
|
||||
- [feature] Added "sqlerror" tween which, in combination with ``pyramid_tm``,
|
||||
can allow Pyramid apps to gracefully handle database server restarts. Any
|
||||
transaction which fails due to a disconnection from the server may be
|
||||
attempted more than once. The assumption is that the second attempt will
|
||||
succeed, since the underlying connection pool will have been invalidated upon
|
||||
the first failure.
|
||||
|
||||
- [feature] Added ``PHONE_TYPE`` enumeration.
|
||||
|
||||
- [general] Added some ``__unicode__()`` methods to database models.
|
||||
|
||||
- [bug] Fixed CSS in the ``progress.mako`` template.
|
||||
|
||||
- [feature] Improved the ``load_spec()`` function so that it raises an
|
||||
``InvalidSpec`` exception if the spec doesn't contain exactly one colon.
|
||||
|
||||
0.1a24
|
||||
------
|
||||
|
||||
- [bug] Fixed bug where creating a new ``Role`` with the UI would use default
|
||||
permissions of the Guest role. Now the default permissions are empty.
|
||||
|
||||
- [bug] Fixed ``User.roles`` UI so that the Guest role is never shown.
|
||||
|
||||
0.1a23
|
||||
------
|
||||
|
||||
- [feature] Added ``capture_output()`` function to ``win32`` module. This is a
|
||||
convenience function which works around an issue when attempting to capture
|
||||
output from a command when the calling application is a Windows GUI app which
|
||||
was launched via the Windows console (DOS terminal).
|
||||
|
||||
- [feature] Updated ``User`` and ``Role`` management views for Pyramid apps.
|
||||
|
||||
0.1a22
|
||||
------
|
||||
|
||||
- [feature] Added a view which allows a user to change his/her password from
|
||||
within Pyramid web apps.
|
||||
|
||||
- [general] Tweaked styles for form validation errors.
|
||||
|
||||
- [general] Added convenience subclass of ``formencode.Schema``.
|
||||
|
||||
- [general] Split CSS styles into ``base.css`` and ``layout.css``, since really
|
||||
they serve different purposes.
|
||||
|
||||
- [feature] Added ``overwriting_move()`` convenience function.
|
||||
|
||||
0.1a21
|
||||
------
|
||||
|
||||
- [feature] Added the ability to specify a Mako template for use when
|
||||
generating error emails. Templates may be referenced in the config file
|
||||
using either a resource path or an absolute file path. If no template is
|
||||
specified, the plain text fallback will be used. The ``content_type`` of the
|
||||
email is also configurable (defaults to ``'text/html'`` if a Mako template is
|
||||
used; ``'text/plain'`` otherwise).
|
||||
|
||||
0.1a20
|
||||
------
|
||||
|
||||
- [feature] Added ability to configure path to the PID file used by file
|
||||
monitor daemons running on Linux. This was necessary to allow multiple
|
||||
daemons to run on the same machine.
|
||||
|
||||
0.1a19
|
||||
------
|
||||
|
||||
- [general] Added ``active_extensions`` stubs to alembic migration script
|
||||
template.
|
||||
|
||||
- [general] Updated pyramid user views and templates. These were sorely out of
|
||||
date.
|
||||
|
||||
0.1a18
|
||||
------
|
||||
|
||||
- [feature] Added ``engine_from_config()`` function to ``edbob.sqlalchemy``
|
||||
module. This was done to provide awareness of the ``poolclass`` parameter to
|
||||
engine configuration.
|
||||
|
||||
0.1a17
|
||||
------
|
||||
|
||||
- [feature] Added ``display()`` method to ``AutocompleteView``. This allows
|
||||
overriding the display text for autocomplete results, in the event that
|
||||
``str(result)`` isn't good enough.
|
||||
|
||||
- [bug] Fixed ``request.has_perm()`` function so that it can check for
|
||||
permissions on the "guest" role, in the event a user is not logged in.
|
||||
|
||||
0.1a16
|
||||
------
|
||||
|
||||
- Add ``alembic/*.py`` to ``MANIFEST.in``.
|
||||
|
||||
- Various progress tweaks.
|
||||
|
||||
- Add ``OrderedDict`` import to ``util`` module.
|
||||
|
||||
0.1a15
|
||||
------
|
||||
|
||||
- Add ``alembic`` to ``db`` dependencies.
|
||||
|
||||
- Fix alembic ``env.py`` template.
|
||||
|
||||
0.1a14
|
||||
------
|
||||
|
||||
- Slight overhaul of init() system; added ``edbob.init_modules()`` function.
|
||||
|
||||
- Added ``read_service()`` method to ``AppConfigParser`` class, for use with
|
||||
Windows services.
|
||||
|
||||
- Added generic ``Service`` class to ``edbob.win32`` module. (File monitor now
|
||||
inherits from it.)
|
||||
|
||||
- Tweaked ``edbob.db`` initialization somewhat. (``Base.metadata`` no longer
|
||||
binds to ``edbob.db.engine``.)
|
||||
|
||||
- Fixed guest role bug in ``edbob.db.auth.has_permission()`` function.
|
||||
|
||||
- Added "automagical" enumeration support for database extensions.
|
||||
|
||||
- Added ``EMAIL_PREFERENCE`` enum to ``contact`` database extension.
|
||||
|
||||
- Tweaked ``edbob.pyramid.includeme()``.
|
||||
|
||||
- Tweaked ``people`` Pyramid views.
|
||||
|
||||
- Added ``edbob.daemon`` module.
|
||||
|
||||
0.1a13
|
||||
------
|
||||
|
||||
|
|
|
@ -19,5 +19,3 @@ recursive-include edbob/pyramid/templates *.mako
|
|||
recursive-include edbob/scaffolds/edbob *.py
|
||||
recursive-include edbob/scaffolds/edbob *_tmpl
|
||||
recursive-include edbob/scaffolds/edbob/+package+/pyramid/templates *.mako
|
||||
|
||||
recursive-include edbob/templates *.mako
|
||||
|
|
|
@ -28,10 +28,12 @@
|
|||
|
||||
|
||||
from edbob._version import __version__
|
||||
from edbob.enum import *
|
||||
from edbob.core import *
|
||||
from edbob.time import *
|
||||
from edbob.files import *
|
||||
from edbob.modules import *
|
||||
from edbob.configuration import *
|
||||
from edbob.initialization import *
|
||||
|
||||
|
||||
inited = False
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = '0.1.2'
|
||||
__version__ = '0.1a13'
|
||||
|
|
|
@ -105,7 +105,6 @@ Options:
|
|||
Config path (may be specified more than once)
|
||||
-n, --no-init Don't load config before executing command
|
||||
-d, --debug Increase logging level to DEBUG
|
||||
-P, --progress Show progress indicators (where relevant)
|
||||
-v, --verbose Increase logging level to INFO
|
||||
-V, --version Display program version and exit
|
||||
|
||||
|
@ -133,7 +132,6 @@ Try '%(name)s help <command>' for more help.""" % self
|
|||
metavar='PATH')
|
||||
parser.add_argument('-d', '--debug', action='store_true', dest='debug')
|
||||
parser.add_argument('-n', '--no-init', action='store_true', default=False)
|
||||
parser.add_argument('-P', '--progress', action='store_true', default=False)
|
||||
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose')
|
||||
parser.add_argument('-V', '--version', action='version',
|
||||
version="%%(prog)s %s" % self.version)
|
||||
|
@ -184,7 +182,6 @@ Try '%(name)s help <command>' for more help.""" % self
|
|||
|
||||
# And finally, do something of real value...
|
||||
cmd = self.subcommands[cmd](parent=self)
|
||||
cmd.show_progress = args.progress
|
||||
cmd._run(*(args.command + args.argv))
|
||||
|
||||
|
||||
|
@ -435,6 +432,11 @@ class FileMonitorCommand(Subcommand):
|
|||
uninstall = subparsers.add_parser('uninstall', help="Uninstall (remove) service")
|
||||
uninstall.set_defaults(subcommand='remove')
|
||||
|
||||
else:
|
||||
parser.add_argument('-D', '--dont-daemonize',
|
||||
action='store_false', dest='daemonize',
|
||||
help="Don't daemonize when starting")
|
||||
|
||||
def get_win32_module(self):
|
||||
from edbob.filemon import win32
|
||||
return win32
|
||||
|
@ -452,10 +454,10 @@ class FileMonitorCommand(Subcommand):
|
|||
from edbob.filemon import linux as filemon
|
||||
|
||||
if args.subcommand == 'start':
|
||||
filemon.start_daemon(self.appname)
|
||||
filemon.start_daemon(self.appname, daemonize=args.daemonize)
|
||||
|
||||
elif args.subcommand == 'stop':
|
||||
filemon.stop_daemon(self.appname)
|
||||
filemon.stop_daemon()
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
from edbob import win32
|
||||
|
|
|
@ -289,25 +289,6 @@ class AppConfigParser(ConfigParser.SafeConfigParser):
|
|||
self.paths_loaded.append(path)
|
||||
log.info("Read config file: %s" % path)
|
||||
|
||||
def read_service(self, service, paths):
|
||||
"""
|
||||
"Special" version of :meth:`read()` which will first inspect the
|
||||
file(s) for a service-specific directive, the presence of which
|
||||
indicates the *true* config file to be used for the service.
|
||||
|
||||
This method is pretty much a hack to get around certain limitations of
|
||||
Windows service implementations; it is not used otherwise.
|
||||
"""
|
||||
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read(paths)
|
||||
|
||||
if (config.has_section('edbob.service_config')
|
||||
and config.has_option('edbob.service_config', service)):
|
||||
paths = eval(config.get('edbob.service_config', service))
|
||||
|
||||
self.read(paths, recurse=True)
|
||||
|
||||
def require(self, section, option, msg=None):
|
||||
"""
|
||||
Convenience method which will raise an exception if the given option
|
||||
|
|
|
@ -38,7 +38,7 @@ class Progress(edbob.Object):
|
|||
"""
|
||||
|
||||
def __init__(self, message, maximum):
|
||||
sys.stderr.write("\n%s...(%u total)\n" % (message, maximum))
|
||||
print >> sys.stderr, "\n%s...(%u total)" % (message, maximum)
|
||||
widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', progressbar.ETA()]
|
||||
self.progress = progressbar.ProgressBar(maxval=maximum, widgets=widgets).start()
|
||||
|
||||
|
@ -47,4 +47,4 @@ class Progress(edbob.Object):
|
|||
return True
|
||||
|
||||
def destroy(self):
|
||||
sys.stderr.write("\n")
|
||||
print >> sys.stderr, ''
|
||||
|
|
|
@ -112,7 +112,7 @@ def graft(target, source, names=None):
|
|||
if hasattr(source, '__all__'):
|
||||
names = source.__all__
|
||||
else:
|
||||
names = [x for x in dir(source) if not x.startswith('_')]
|
||||
names = dir(source)
|
||||
elif isinstance(names, basestring):
|
||||
names = [names]
|
||||
|
||||
|
|
137
edbob/daemon.py
137
edbob/daemon.py
|
@ -1,137 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# This code was (mostly, with some tweaks) stolen from:
|
||||
# http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
|
||||
|
||||
import sys, os, time, atexit
|
||||
import stat
|
||||
from signal import SIGTERM
|
||||
|
||||
class Daemon:
|
||||
"""
|
||||
A generic daemon class.
|
||||
|
||||
Usage: subclass the Daemon class and override the run() method
|
||||
"""
|
||||
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.pidfile = pidfile
|
||||
|
||||
def daemonize(self):
|
||||
"""
|
||||
do the UNIX double-fork magic, see Stevens' "Advanced
|
||||
Programming in the UNIX Environment" for details (ISBN 0201563177)
|
||||
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
||||
"""
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit first parent
|
||||
sys.exit(0)
|
||||
except OSError, e:
|
||||
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# decouple from parent environment
|
||||
os.chdir("/")
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
# do second fork
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit from second parent
|
||||
sys.exit(0)
|
||||
except OSError, e:
|
||||
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# redirect standard file descriptors
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
si = file(self.stdin, 'r')
|
||||
so = file(self.stdout, 'a+')
|
||||
se = file(self.stderr, 'a+', 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
# write pidfile
|
||||
atexit.register(self.delpid)
|
||||
pid = str(os.getpid())
|
||||
file(self.pidfile,'w+').write("%s\n" % pid)
|
||||
os.chmod(self.pidfile, stat.S_IRUSR|stat.S_IWUSR)
|
||||
|
||||
def delpid(self):
|
||||
os.remove(self.pidfile)
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the daemon
|
||||
"""
|
||||
# Check for a pidfile to see if the daemon already runs
|
||||
try:
|
||||
pf = file(self.pidfile,'r')
|
||||
pid = int(pf.read().strip())
|
||||
pf.close()
|
||||
except IOError:
|
||||
pid = None
|
||||
|
||||
if pid:
|
||||
message = "pidfile %s already exist. Daemon already running?\n"
|
||||
sys.stderr.write(message % self.pidfile)
|
||||
sys.exit(1)
|
||||
|
||||
# Start the daemon
|
||||
self.daemonize()
|
||||
self.run()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the daemon
|
||||
"""
|
||||
# Get the pid from the pidfile
|
||||
try:
|
||||
pf = file(self.pidfile,'r')
|
||||
pid = int(pf.read().strip())
|
||||
pf.close()
|
||||
except IOError:
|
||||
pid = None
|
||||
|
||||
if not pid:
|
||||
message = "pidfile %s does not exist. Daemon not running?\n"
|
||||
sys.stderr.write(message % self.pidfile)
|
||||
return # not an error in a restart
|
||||
|
||||
# Try killing the daemon process
|
||||
try:
|
||||
while 1:
|
||||
os.kill(pid, SIGTERM)
|
||||
time.sleep(0.1)
|
||||
except OSError, err:
|
||||
err = str(err)
|
||||
if err.find("No such process") > 0:
|
||||
if os.path.exists(self.pidfile):
|
||||
os.remove(self.pidfile)
|
||||
else:
|
||||
print str(err)
|
||||
sys.exit(1)
|
||||
|
||||
def restart(self):
|
||||
"""
|
||||
Restart the daemon
|
||||
"""
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
You should override this method when you subclass Daemon. It will be called after the process has been
|
||||
daemonized by start() or restart().
|
||||
"""
|
|
@ -28,16 +28,16 @@
|
|||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import engine_from_config, MetaData
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
import edbob
|
||||
from edbob.sqlalchemy import engine_from_config
|
||||
|
||||
|
||||
__all__ = ['engines', 'engine', 'Session', 'get_setting', 'save_setting']
|
||||
|
||||
inited = False
|
||||
engines = None
|
||||
engine = None
|
||||
Session = sessionmaker()
|
||||
|
@ -71,36 +71,36 @@ def init(config):
|
|||
from edbob.db import enum
|
||||
from edbob.db.extensions import extend_framework
|
||||
|
||||
global engines, engine
|
||||
global inited, engines, engine
|
||||
|
||||
keys = config.get('edbob.db', 'keys')
|
||||
keys = config.get('edbob.db', 'sqlalchemy.keys')
|
||||
if keys:
|
||||
keys = keys.split(',')
|
||||
keys = keys.split()
|
||||
else:
|
||||
keys = ['default']
|
||||
|
||||
engines = {}
|
||||
cfg = config.get_dict('edbob.db')
|
||||
for key in keys:
|
||||
key = key.strip()
|
||||
try:
|
||||
engines[key] = engine_from_config(cfg, '%s.' % key)
|
||||
engines[key] = engine_from_config(cfg, 'sqlalchemy.%s.' % key)
|
||||
except KeyError:
|
||||
if key == 'default':
|
||||
try:
|
||||
engines[key] = engine_from_config(cfg, 'sqlalchemy.')
|
||||
engines[key] = engine_from_config(cfg)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
engine = engines.get('default')
|
||||
if engine:
|
||||
Session.configure(bind=engine)
|
||||
Base.metadata.bind = engine
|
||||
|
||||
extend_framework()
|
||||
|
||||
edbob.graft(edbob, edbob.db)
|
||||
edbob.graft(edbob, model)
|
||||
edbob.graft(edbob, enum)
|
||||
inited = True
|
||||
|
||||
|
||||
def get_setting(name, session=None):
|
||||
|
|
|
@ -85,12 +85,12 @@ def guest_role(session):
|
|||
"""
|
||||
|
||||
uuid = 'f8a27c98965a11dfaff7001143047286'
|
||||
guest = session.query(edbob.Role).get(uuid)
|
||||
if guest:
|
||||
return guest
|
||||
guest = edbob.Role(uuid=uuid, name='Guest')
|
||||
session.add(guest)
|
||||
return guest
|
||||
admin = session.query(edbob.Role).get(uuid)
|
||||
if admin:
|
||||
return admin
|
||||
admin = edbob.Role(uuid=uuid, name='Guest')
|
||||
session.add(admin)
|
||||
return admin
|
||||
|
||||
|
||||
def grant_permission(role, permission, session=None):
|
||||
|
@ -105,7 +105,7 @@ def grant_permission(role, permission, session=None):
|
|||
role.permissions.append(permission)
|
||||
|
||||
|
||||
def has_permission(obj, perm, include_guest=True, session=None):
|
||||
def has_permission(obj, perm, session=None):
|
||||
"""
|
||||
Checks the given ``obj`` (which may be either a :class:`edbob.User`` or
|
||||
:class:`edbob.Role` instance), and returns a boolean indicating whether or
|
||||
|
@ -114,7 +114,7 @@ def has_permission(obj, perm, include_guest=True, session=None):
|
|||
"""
|
||||
|
||||
if isinstance(obj, edbob.User):
|
||||
roles = list(obj.roles)
|
||||
roles = obj.roles
|
||||
elif isinstance(obj, edbob.Role):
|
||||
roles = [obj]
|
||||
elif obj is None:
|
||||
|
@ -124,9 +124,8 @@ def has_permission(obj, perm, include_guest=True, session=None):
|
|||
if not session:
|
||||
session = object_session(obj)
|
||||
assert session
|
||||
if include_guest:
|
||||
roles.append(guest_role(session))
|
||||
admin = administrator_role(session)
|
||||
roles.append(guest_role(session))
|
||||
for role in roles:
|
||||
if role is admin:
|
||||
return True
|
||||
|
|
|
@ -76,11 +76,6 @@ class Extension(edbob.Object):
|
|||
# :meth:`Extension.get_models_module()` for more info).
|
||||
model_module = ''
|
||||
|
||||
# You can set this to any dotted module path you like. If unset a default
|
||||
# will be assumed, of the form ``<path.to.extension>.enum`` (see
|
||||
# :meth:`Extension.get_enum_module()` for more info).
|
||||
enum_module = ''
|
||||
|
||||
# @property
|
||||
# @requires_impl(is_property=True)
|
||||
# def name(self):
|
||||
|
@ -124,9 +119,6 @@ class Extension(edbob.Object):
|
|||
"""
|
||||
|
||||
edbob.graft(edbob, self.get_model_module())
|
||||
enum = self.get_enum_module()
|
||||
if enum:
|
||||
edbob.graft(edbob, enum)
|
||||
|
||||
# def extend_mappers(self, metadata):
|
||||
# """
|
||||
|
@ -152,26 +144,6 @@ class Extension(edbob.Object):
|
|||
self.populate_metadata(meta, recurse)
|
||||
return meta
|
||||
|
||||
def get_enum_module(self):
|
||||
"""
|
||||
Imports and returns a reference to the Python module providing
|
||||
enumeration data for the extension (if one exists).
|
||||
|
||||
:attr:`Extension.enum_module` is first consulted to determine the
|
||||
dotted module path. If nothing is found there, a default path is
|
||||
constructed by appending ``'.enum'`` to the extension module's own
|
||||
dotted path.
|
||||
"""
|
||||
|
||||
if self.enum_module:
|
||||
module = self.enum_module
|
||||
else:
|
||||
module = str(self.__class__.__module__) + '.enum'
|
||||
try:
|
||||
return import_module_path(module)
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
def get_model_module(self):
|
||||
"""
|
||||
Imports and returns a reference to the Python module providing schema
|
||||
|
@ -366,7 +338,7 @@ def extend_framework():
|
|||
session.close()
|
||||
|
||||
for name in sorted(extensions, extension_sorter(extensions)):
|
||||
log.debug("Applying active extension: %s" % name)
|
||||
log.info("Applying active extension: %s" % name)
|
||||
ext = extensions[name]
|
||||
# merge_extension_metadata(ext)
|
||||
# ext.extend_classes()
|
||||
|
|
|
@ -51,11 +51,10 @@ class Permission(Base):
|
|||
permission = Column(String(50), primary_key=True)
|
||||
|
||||
def __repr__(self):
|
||||
return "Permission(role_uuid={0}, permission={1})".format(
|
||||
repr(self.role_uuid), repr(self.permission))
|
||||
return "<Permission: %s, %s>" % (self.role, self.permission)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.permission or '')
|
||||
def __str__(self):
|
||||
return str(self.permission or '')
|
||||
|
||||
|
||||
class UserRole(Base):
|
||||
|
@ -70,7 +69,7 @@ class UserRole(Base):
|
|||
role_uuid = Column(String(32), ForeignKey('roles.uuid'))
|
||||
|
||||
def __repr__(self):
|
||||
return "UserRole(uuid={0})".format(repr(self.uuid))
|
||||
return "<UserRole: %s : %s>" % (self.user, self.role)
|
||||
|
||||
|
||||
class Role(Base):
|
||||
|
@ -90,18 +89,16 @@ class Role(Base):
|
|||
creator=lambda x: Permission(permission=x),
|
||||
getset_factory=getset_factory)
|
||||
|
||||
_users = relationship(
|
||||
UserRole, backref='role',
|
||||
cascade='save-update, merge, delete, delete-orphan')
|
||||
_users = relationship(UserRole, backref='role')
|
||||
users = association_proxy('_users', 'user',
|
||||
creator=lambda x: UserRole(user=x),
|
||||
getset_factory=getset_factory)
|
||||
|
||||
def __repr__(self):
|
||||
return "Role(uuid={0})".format(repr(self.uuid))
|
||||
return "<Role: %s>" % self.name
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.name or '')
|
||||
def __str__(self):
|
||||
return str(self.name or '')
|
||||
|
||||
|
||||
class User(Base):
|
||||
|
@ -125,18 +122,17 @@ class User(Base):
|
|||
getset_factory=getset_factory)
|
||||
|
||||
def __repr__(self):
|
||||
return "User(uuid={0})".format(repr(self.uuid))
|
||||
return "<User: %s>" % self.username
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.username or '')
|
||||
def __str__(self):
|
||||
return str(self.username or '')
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
"""
|
||||
Returns :attr:`Person.display_name` if present; otherwise returns
|
||||
:attr:`username`.
|
||||
Returns the user's ``person.display_name``, if present, otherwise the
|
||||
``username``.
|
||||
"""
|
||||
|
||||
if self.person and self.person.display_name:
|
||||
return self.person.display_name
|
||||
return self.username
|
||||
|
|
|
@ -72,8 +72,7 @@ class PhoneNumber(Base):
|
|||
__mapper_args__ = {'polymorphic_on': parent_type}
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}(uuid={1})".format(
|
||||
self.__class__.__name__, repr(self.uuid))
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.number)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.number)
|
||||
|
@ -104,8 +103,7 @@ class EmailAddress(Base):
|
|||
__mapper_args__ = {'polymorphic_on': parent_type}
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}(uuid={1})".format(
|
||||
self.__class__.__name__, repr(self.uuid))
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.address)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.address)
|
||||
|
@ -133,7 +131,7 @@ class Person(Base):
|
|||
display_name = Column(String(100), default=get_person_display_name)
|
||||
|
||||
def __repr__(self):
|
||||
return "Person(uuid={0})".format(repr(self.uuid))
|
||||
return "<Person: %s>" % self.display_name
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.display_name or '')
|
||||
|
|
|
@ -54,7 +54,7 @@ class ActiveExtension(Base):
|
|||
name = Column(String(50), primary_key=True)
|
||||
|
||||
def __repr__(self):
|
||||
return "ActiveExtension(name={0})".format(repr(self.name))
|
||||
return "<ActiveExtension: %s>" % self.name
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name or '')
|
||||
|
@ -71,4 +71,4 @@ class Setting(Base):
|
|||
value = Column(Text)
|
||||
|
||||
def __repr__(self):
|
||||
return "Setting(name={0})".format(repr(self.name))
|
||||
return "<Setting: %s>" % self.name
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.enum`` -- Enumerations
|
||||
"""
|
||||
|
||||
|
||||
EMAIL_PREFERENCE_NONE = 0
|
||||
EMAIL_PREFERENCE_TEXT = 1
|
||||
EMAIL_PREFERENCE_HTML = 2
|
||||
EMAIL_PREFERENCE_MOBILE = 3
|
||||
|
||||
EMAIL_PREFERENCE = {
|
||||
EMAIL_PREFERENCE_NONE : "No Emails",
|
||||
EMAIL_PREFERENCE_TEXT : "Text",
|
||||
EMAIL_PREFERENCE_HTML : "HTML",
|
||||
EMAIL_PREFERENCE_MOBILE : "Mobile",
|
||||
}
|
||||
|
||||
|
||||
PHONE_TYPE_HOME = 'home'
|
||||
PHONE_TYPE_MOBILE = 'mobile'
|
||||
PHONE_TYPE_OTHER = 'other'
|
||||
|
||||
PHONE_TYPE = {
|
||||
PHONE_TYPE_HOME : "Home",
|
||||
PHONE_TYPE_MOBILE : "Mobile",
|
||||
PHONE_TYPE_OTHER : "Other",
|
||||
}
|
|
@ -26,7 +26,6 @@
|
|||
``edbob.errors`` -- Error Alert Emails
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
import socket
|
||||
import logging
|
||||
|
@ -34,7 +33,6 @@ from traceback import format_exception
|
|||
from cStringIO import StringIO
|
||||
|
||||
import edbob
|
||||
from edbob.files import resource_path
|
||||
from edbob.mail import sendmail_with_config
|
||||
|
||||
|
||||
|
@ -62,52 +60,15 @@ def email_exception(type=None, value=None, traceback=None):
|
|||
if not (type and value and traceback):
|
||||
type, value, traceback = sys.exc_info()
|
||||
|
||||
hostname = socket.gethostname()
|
||||
traceback = ''.join(format_exception(type, value, traceback))
|
||||
traceback = traceback.strip()
|
||||
data = {
|
||||
'host_name': hostname,
|
||||
'host_ip': socket.gethostbyname(hostname),
|
||||
'host_time': edbob.local_time(),
|
||||
'traceback': traceback,
|
||||
}
|
||||
|
||||
body, ctype = render_exception(data)
|
||||
ctype = edbob.config.get('edbob.errors', 'content_type', default=ctype)
|
||||
sendmail_with_config('errors', body, content_type=ctype)
|
||||
|
||||
|
||||
def render_exception(data):
|
||||
"""
|
||||
Renders the exception data using a Mako template if one is configured;
|
||||
otherwise as a simple string.
|
||||
"""
|
||||
|
||||
template = edbob.config.get('edbob.errors', 'template')
|
||||
if template:
|
||||
template = resource_path(template)
|
||||
if os.path.exists(template):
|
||||
|
||||
# Assume Mako template; render and return.
|
||||
from mako.template import Template
|
||||
template = Template(filename=template)
|
||||
return template.render(**data), 'text/html'
|
||||
|
||||
# If not a Mako template, return regular text with substitutions.
|
||||
body = StringIO()
|
||||
data['host_time'] = data['host_time'].strftime('%Y-%m-%d %H:%M:%S %Z%z')
|
||||
|
||||
body.write("""\
|
||||
An unhandled exception occurred.
|
||||
hostname = socket.gethostname()
|
||||
body.write("An exception occurred.\n")
|
||||
body.write("\n")
|
||||
body.write("Machine Name: %s (%s)\n" % (hostname, socket.gethostbyname(hostname)))
|
||||
body.write("Local Time: %s\n" % (edbob.local_time().strftime('%Y-%m-%d %H:%M:%S %Z%z')))
|
||||
body.write("\n")
|
||||
body.write("%s\n" % ''.join(format_exception(type, value, traceback)))
|
||||
|
||||
Machine Name: %(host_name)s (%(host_ip)s)
|
||||
|
||||
Machine Time: %(host_time)s
|
||||
|
||||
%(traceback)s
|
||||
""" % data)
|
||||
|
||||
b = body.getvalue()
|
||||
sendmail_with_config('errors', body.getvalue())
|
||||
body.close()
|
||||
|
||||
return b, 'text/plain'
|
||||
|
|
|
@ -73,12 +73,6 @@ class LoadSpecError(Exception):
|
|||
return None
|
||||
|
||||
|
||||
class InvalidSpec(LoadSpecError):
|
||||
|
||||
def specifics(self):
|
||||
return "invalid spec"
|
||||
|
||||
|
||||
class ModuleMissingAttribute(LoadSpecError):
|
||||
"""
|
||||
Raised during :func:`edbob.load_spec()` when the module imported okay but
|
||||
|
|
|
@ -26,18 +26,10 @@
|
|||
``edbob.filemon`` -- File Monitoring Service
|
||||
"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import Queue
|
||||
import logging
|
||||
|
||||
import edbob
|
||||
from edbob.errors import email_exception
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import win32api
|
||||
from edbob.win32 import file_is_free
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -73,12 +65,6 @@ class MonitorProfile(object):
|
|||
self.locks = edbob.config.getboolean(
|
||||
'%s.filemon' % appname, '%s.locks' % key, default=False)
|
||||
|
||||
self.process_existing = edbob.config.getboolean(
|
||||
'%s.filemon' % appname, '%s.process_existing' % key, default=True)
|
||||
|
||||
self.stop_on_error = edbob.config.getboolean(
|
||||
'%s.filemon' % appname, '%s.stop_on_error' % key, default=False)
|
||||
|
||||
|
||||
def get_monitor_profiles(appname):
|
||||
"""
|
||||
|
@ -124,105 +110,3 @@ def get_monitor_profiles(appname):
|
|||
del monitored[key]
|
||||
|
||||
return monitored
|
||||
|
||||
|
||||
def queue_existing(profile, path):
|
||||
"""
|
||||
Adds files found in a watched folder to a processing queue. This is called
|
||||
when the monitor first starts, to handle the case of files which exist
|
||||
prior to startup.
|
||||
|
||||
If files are found, they are first sorted by modification timestamp, using
|
||||
a lexical sort on the filename as a tie-breaker, and then added to the
|
||||
queue in that order.
|
||||
|
||||
:param profile: Monitor profile for which the folder is to be watched. The
|
||||
profile is expected to already have a queue attached; any existing files
|
||||
will be added to this queue.
|
||||
:type profile: :class:`edbob.filemon.MonitorProfile` instance
|
||||
|
||||
:param path: Folder path which is to be checked for files.
|
||||
:type path: string
|
||||
|
||||
:returns: ``None``
|
||||
"""
|
||||
|
||||
def sorter(x, y):
|
||||
mtime_x = os.path.getmtime(x)
|
||||
mtime_y = os.path.getmtime(y)
|
||||
if mtime_x < mtime_y:
|
||||
return -1
|
||||
if mtime_x > mtime_y:
|
||||
return 1
|
||||
return cmp(x, y)
|
||||
|
||||
paths = [os.path.join(path, x) for x in os.listdir(path)]
|
||||
for path in sorted(paths, cmp=sorter):
|
||||
|
||||
# Only process normal files.
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
|
||||
# If using locks, don't process "in transit" files.
|
||||
if profile.locks and path.endswith('.lock'):
|
||||
continue
|
||||
|
||||
log.debug("queue_existing: queuing existing file for "
|
||||
"profile '%s': %s" % (profile.key, path))
|
||||
profile.queue.put(path)
|
||||
|
||||
|
||||
def perform_actions(profile):
|
||||
"""
|
||||
Callable target for action threads.
|
||||
"""
|
||||
|
||||
keep_going = True
|
||||
while keep_going:
|
||||
|
||||
try:
|
||||
path = profile.queue.get_nowait()
|
||||
except Queue.Empty:
|
||||
pass
|
||||
else:
|
||||
|
||||
# In some cases, processing one file may cause other related files
|
||||
# to also be processed. When this happens, a path on the queue may
|
||||
# point to a file which no longer exists.
|
||||
if not os.path.exists(path):
|
||||
log.info("perform_actions: path does not exist: %s" % path)
|
||||
continue
|
||||
|
||||
log.debug("perform_actions: processing file: %s" % path)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
while not file_is_free(path):
|
||||
win32api.Sleep(0)
|
||||
|
||||
for spec, func, args in profile.actions:
|
||||
|
||||
log.info("perform_actions: calling function '%s' on file: %s" %
|
||||
(spec, path))
|
||||
|
||||
try:
|
||||
func(path, *args)
|
||||
|
||||
except:
|
||||
log.exception("perform_actions: exception occurred "
|
||||
"while processing file: %s" % path)
|
||||
email_exception()
|
||||
|
||||
# Don't process any more files if the profile is so
|
||||
# configured.
|
||||
if profile.stop_on_error:
|
||||
keep_going = False
|
||||
|
||||
# Either way this particular file probably shouldn't be
|
||||
# processed any further.
|
||||
log.warning("perform_actions: no further processing "
|
||||
"will be done for file: %s" % path)
|
||||
break
|
||||
|
||||
log.warning("perform_actions: error encountered, and configuration "
|
||||
"dictates that no more actions will be processed for "
|
||||
"profile: %s" % profile.key)
|
||||
|
|
|
@ -27,24 +27,14 @@
|
|||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import threading
|
||||
import Queue
|
||||
import signal
|
||||
import logging
|
||||
|
||||
try:
|
||||
import pyinotify
|
||||
except ImportError:
|
||||
# Mock out for testing on Windows.
|
||||
class Dummy(object):
|
||||
pass
|
||||
pyinotify = Dummy()
|
||||
pyinotify.ProcessEvent = Dummy
|
||||
import pyinotify
|
||||
|
||||
import edbob
|
||||
from edbob import filemon
|
||||
from edbob.daemon import Daemon
|
||||
from edbob.errors import email_exception
|
||||
from edbob.filemon import get_monitor_profiles
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -55,8 +45,9 @@ class EventHandler(pyinotify.ProcessEvent):
|
|||
Event processor for file monitor daemon.
|
||||
"""
|
||||
|
||||
def my_init(self, profile=None, **kwargs):
|
||||
self.profile = profile
|
||||
def my_init(self, actions=[], locks=False, **kwargs):
|
||||
self.actions = actions
|
||||
self.locks = locks
|
||||
|
||||
def process_IN_ACCESS(self, event):
|
||||
log.debug("EventHandler: IN_ACCESS: %s" % event.pathname)
|
||||
|
@ -66,85 +57,84 @@ class EventHandler(pyinotify.ProcessEvent):
|
|||
|
||||
def process_IN_CLOSE_WRITE(self, event):
|
||||
log.debug("EventHandler: IN_CLOSE_WRITE: %s" % event.pathname)
|
||||
if not self.profile.locks:
|
||||
self.profile.queue.put(event.pathname)
|
||||
if not self.locks:
|
||||
self.perform_actions(event.pathname)
|
||||
|
||||
def process_IN_CREATE(self, event):
|
||||
log.debug("EventHandler: IN_CREATE: %s" % event.pathname)
|
||||
|
||||
def process_IN_DELETE(self, event):
|
||||
log.debug("EventHandler: IN_DELETE: %s" % event.pathname)
|
||||
if self.profile.locks and event.pathname.endswith('.lock'):
|
||||
self.profile.queue.put(event.pathname[:-5])
|
||||
if self.locks and event.pathname.endswith('.lock'):
|
||||
self.perform_actions(event.pathname[:-5])
|
||||
|
||||
def process_IN_MODIFY(self, event):
|
||||
log.debug("EventHandler: IN_MODIFY: %s" % event.pathname)
|
||||
|
||||
def process_IN_MOVED_TO(self, event):
|
||||
log.debug("EventHandler: IN_MOVED_TO: %s" % event.pathname)
|
||||
if not self.profile.locks:
|
||||
self.profile.queue.put(event.pathname)
|
||||
if not self.locks:
|
||||
self.perform_actions(event.pathname)
|
||||
|
||||
def perform_actions(self, path):
|
||||
for spec, func, args in self.actions:
|
||||
func(path, *args)
|
||||
|
||||
|
||||
class FileMonitorDaemon(Daemon):
|
||||
def get_pid_path():
|
||||
"""
|
||||
Returns the path to the PID file for the file monitor daemon.
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
|
||||
wm = pyinotify.WatchManager()
|
||||
notifier = pyinotify.Notifier(wm)
|
||||
|
||||
mask = (pyinotify.IN_ACCESS
|
||||
| pyinotify.IN_ATTRIB
|
||||
| pyinotify.IN_CLOSE_WRITE
|
||||
| pyinotify.IN_CREATE
|
||||
| pyinotify.IN_DELETE
|
||||
| pyinotify.IN_MODIFY
|
||||
| pyinotify.IN_MOVED_TO)
|
||||
|
||||
monitored = filemon.get_monitor_profiles(self.appname)
|
||||
for key, profile in monitored.iteritems():
|
||||
|
||||
# Create a file queue for the profile.
|
||||
profile.queue = Queue.Queue()
|
||||
|
||||
# Perform setup for each of the watched folders.
|
||||
for path in profile.dirs:
|
||||
|
||||
# Maybe put all pre-existing files in the queue.
|
||||
if profile.process_existing:
|
||||
filemon.queue_existing(profile, path)
|
||||
|
||||
# Create a watch for the folder.
|
||||
log.debug("start_daemon: profile '%s' watches folder: %s" % (key, path))
|
||||
wm.add_watch(path, mask, proc_fun=EventHandler(profile=profile))
|
||||
|
||||
# Create an action thread for the profile.
|
||||
name = 'actions-%s' % key
|
||||
log.debug("start_daemon: starting action thread: %s" % name)
|
||||
thread = threading.Thread(target=filemon.perform_actions,
|
||||
name=name, args=(profile,))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
# Fire up the watchers.
|
||||
notifier.loop()
|
||||
basename = os.path.basename(sys.argv[0])
|
||||
return '/tmp/%s_filemon.pid' % basename
|
||||
|
||||
|
||||
def get_daemon(appname=None):
|
||||
if appname is None:
|
||||
appname = os.path.basename(sys.argv[0])
|
||||
pid_path = edbob.config.get('%s.filemon' % appname, 'pid_path')
|
||||
if not pid_path:
|
||||
pid_path = '/tmp/%s_filemon.pid' % appname
|
||||
def start_daemon(appname, daemonize=True):
|
||||
"""
|
||||
Starts the file monitor daemon.
|
||||
"""
|
||||
|
||||
monitor = FileMonitorDaemon(pid_path)
|
||||
monitor.appname = appname
|
||||
return monitor
|
||||
pid_path = get_pid_path()
|
||||
if os.path.exists(pid_path):
|
||||
print "File monitor is already running"
|
||||
return
|
||||
|
||||
wm = pyinotify.WatchManager()
|
||||
notifier = pyinotify.Notifier(wm)
|
||||
|
||||
monitored = get_monitor_profiles(appname)
|
||||
|
||||
mask = (pyinotify.IN_ACCESS | pyinotify.IN_ATTRIB
|
||||
| pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CREATE
|
||||
| pyinotify.IN_DELETE | pyinotify.IN_MODIFY
|
||||
| pyinotify.IN_MOVED_TO)
|
||||
for profile in monitored.itervalues():
|
||||
for path in profile.dirs:
|
||||
wm.add_watch(path, mask, proc_fun=EventHandler(
|
||||
actions=profile.actions, locks=profile.locks))
|
||||
|
||||
if not daemonize:
|
||||
sys.stderr.write("Starting file monitor. (Press Ctrl+C to quit.)\n")
|
||||
notifier.loop(daemonize=daemonize, pid_file=pid_path)
|
||||
|
||||
|
||||
def start_daemon(appname):
|
||||
get_daemon(appname).start()
|
||||
def stop_daemon():
|
||||
"""
|
||||
Stops the file monitor daemon.
|
||||
"""
|
||||
|
||||
pid_path = get_pid_path()
|
||||
if not os.path.exists(pid_path):
|
||||
print "File monitor is not running"
|
||||
return
|
||||
|
||||
def stop_daemon(appname):
|
||||
get_daemon(appname).stop()
|
||||
f = open(pid_path)
|
||||
pid = f.read().strip()
|
||||
f.close()
|
||||
if not pid.isdigit():
|
||||
log.warning("stop_daemon: Found bogus PID (%s) in file: %s" % (pid, pid_path))
|
||||
return
|
||||
|
||||
os.kill(int(pid), signal.SIGKILL)
|
||||
os.remove(pid_path)
|
||||
|
|
|
@ -33,9 +33,9 @@ import logging
|
|||
import threading
|
||||
|
||||
import edbob
|
||||
from edbob import filemon
|
||||
from edbob.errors import email_exception
|
||||
from edbob.win32 import Service, file_is_free
|
||||
from edbob.filemon import get_monitor_profiles
|
||||
from edbob.win32 import file_is_free
|
||||
|
||||
if sys.platform == 'win32': # docs should build for everyone
|
||||
import win32api
|
||||
|
@ -50,26 +50,39 @@ if sys.platform == 'win32': # docs should build for everyone
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileMonitorService(Service):
|
||||
class FileMonitorService(win32serviceutil.ServiceFramework):
|
||||
"""
|
||||
Implements edbob's file monitor Windows service.
|
||||
"""
|
||||
|
||||
_svc_name_ = 'EdbobFileMonitor'
|
||||
_svc_name_ = "Edbob File Monitor"
|
||||
_svc_display_name_ = "Edbob : File Monitoring Service"
|
||||
_svc_description_ = ("Monitors one or more folders for incoming files, "
|
||||
"and performs configured actions as new files arrive.")
|
||||
|
||||
appname = 'edbob'
|
||||
|
||||
def __init__(self, args):
|
||||
"""
|
||||
Constructor.
|
||||
"""
|
||||
|
||||
# super(FileMonitorService, self).__init__(args)
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
|
||||
# Create "wait stop" event, for main worker loop.
|
||||
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
||||
|
||||
def Initialize(self):
|
||||
"""
|
||||
Service initialization.
|
||||
"""
|
||||
|
||||
if not Service.Initialize(self):
|
||||
return False
|
||||
# Read configuration file(s).
|
||||
edbob.init(self.appname)
|
||||
|
||||
# Read monitor profile(s) from config.
|
||||
self.monitored = filemon.get_monitor_profiles(self.appname)
|
||||
self.monitored = get_monitor_profiles(self.appname)
|
||||
|
||||
# Make sure we have something to do.
|
||||
if not self.monitored:
|
||||
|
@ -79,36 +92,78 @@ class FileMonitorService(Service):
|
|||
for key, profile in self.monitored.iteritems():
|
||||
|
||||
# Create a file queue for the profile.
|
||||
profile.queue = Queue.Queue()
|
||||
queue = Queue.Queue()
|
||||
|
||||
# Perform setup for each of the watched folders.
|
||||
# Create a monitor thread for each folder in profile.
|
||||
for i, path in enumerate(profile.dirs, 1):
|
||||
|
||||
# Maybe put all pre-existing files in the queue.
|
||||
if profile.process_existing:
|
||||
filemon.queue_existing(profile, path)
|
||||
|
||||
# Create a monitor thread for the folder.
|
||||
name = 'monitor-%s-%u' % (key, i)
|
||||
log.debug("Initialize: Starting '%s' thread for folder: %s" %
|
||||
(name, path))
|
||||
thread = threading.Thread(target=monitor_files,
|
||||
name=name, args=(profile, path))
|
||||
thread = threading.Thread(
|
||||
target=monitor_files,
|
||||
name=name,
|
||||
args=(queue, path, profile))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
# Create an action thread for the profile.
|
||||
name = 'actions-%s' % key
|
||||
log.debug("Initialize: Starting '%s' thread" % name)
|
||||
thread = threading.Thread(target=filemon.perform_actions,
|
||||
name=name, args=(profile,))
|
||||
thread = threading.Thread(
|
||||
target=perform_actions,
|
||||
name=name,
|
||||
args=(queue, profile))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
return True
|
||||
|
||||
def SvcDoRun(self):
|
||||
"""
|
||||
This method is invoked when the service starts.
|
||||
"""
|
||||
|
||||
import servicemanager
|
||||
|
||||
# Write start occurrence to Windows Event Log.
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ''))
|
||||
|
||||
# Figure out what we're supposed to be doing.
|
||||
if self.Initialize():
|
||||
|
||||
# Wait infinitely for stop request, while threads do their thing.
|
||||
log.info("SvcDoRun: All threads started; waiting for stop request.")
|
||||
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
|
||||
log.info("SvcDoRun: Stop request received.")
|
||||
|
||||
else: # Nothing to be done...
|
||||
msg = "Nothing to do! No valid monitor profiles found in config."
|
||||
servicemanager.LogWarningMsg(msg)
|
||||
log.warning("SvcDoRun: %s" % msg)
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
|
||||
# Write stop occurrence to Windows Event Log.
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STOPPED,
|
||||
(self._svc_name_, ''))
|
||||
|
||||
def SvcStop(self):
|
||||
"""
|
||||
This method is invoked when the service is requested to stop itself.
|
||||
"""
|
||||
|
||||
# Let the SCM know we're trying to stop.
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
|
||||
# Let worker loop know its job is done.
|
||||
win32event.SetEvent(self.hWaitStop)
|
||||
|
||||
|
||||
def monitor_files(profile, path):
|
||||
def monitor_files(queue, path, profile):
|
||||
"""
|
||||
Callable target for file monitor threads.
|
||||
"""
|
||||
|
@ -140,7 +195,40 @@ def monitor_files(profile, path):
|
|||
winnt.FILE_ACTION_RENAMED_NEW_NAME):
|
||||
log.debug("monitor_files: Queueing '%s' file: %s" %
|
||||
(profile.key, fpath))
|
||||
profile.queue.put(fpath)
|
||||
queue.put(fpath)
|
||||
|
||||
|
||||
def perform_actions(queue, profile):
|
||||
"""
|
||||
Callable target for action threads.
|
||||
"""
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
path = queue.get_nowait()
|
||||
except Queue.Empty:
|
||||
pass
|
||||
else:
|
||||
|
||||
while not file_is_free(path):
|
||||
win32api.Sleep(0)
|
||||
|
||||
for spec, func, args in profile.actions:
|
||||
|
||||
log.info("perform_actions: Calling function '%s' on file: %s" %
|
||||
(spec, path))
|
||||
|
||||
try:
|
||||
func(path, *args)
|
||||
|
||||
except:
|
||||
log.exception("perform_actions: An exception occurred "
|
||||
"while processing file: %s" % path)
|
||||
email_exception()
|
||||
|
||||
# This file probably shouldn't be processed any further.
|
||||
break
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -33,8 +33,6 @@ import shutil
|
|||
import tempfile
|
||||
import lockfile
|
||||
|
||||
import pkg_resources
|
||||
|
||||
|
||||
__all__ = ['temp_path']
|
||||
|
||||
|
@ -100,31 +98,6 @@ def count_lines(path):
|
|||
return lines
|
||||
|
||||
|
||||
def overwriting_move(src, dst):
|
||||
"""
|
||||
Convenience function which is equivalent to ``shutil.move()``, except it
|
||||
will cause the destination file to be overwritten if it exists.
|
||||
"""
|
||||
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
if os.path.exists(dst):
|
||||
os.remove(dst)
|
||||
shutil.move(src, dst)
|
||||
|
||||
|
||||
def resource_path(path):
|
||||
"""
|
||||
Returns a resource file path. ``path`` is assumed either to be a package
|
||||
resource, or a regular file path. In the latter case it is returned
|
||||
unchanged.
|
||||
"""
|
||||
|
||||
if not os.path.isabs(path) and ':' in path:
|
||||
return pkg_resources.resource_filename(*path.split(':'))
|
||||
return path
|
||||
|
||||
|
||||
def temp_path(suffix='.tmp', prefix='edbob.'):
|
||||
"""
|
||||
Convenience function to return a temporary file path. The arguments'
|
||||
|
|
|
@ -38,9 +38,7 @@ from edbob.configuration import (
|
|||
from edbob.exceptions import InitError
|
||||
|
||||
|
||||
__all__ = ['init', 'init_modules', 'inited']
|
||||
|
||||
inited = []
|
||||
__all__ = ['init']
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -82,41 +80,22 @@ def init(appname='edbob', *args, **kwargs):
|
|||
else:
|
||||
config_paths = default_system_paths(appname) + default_user_paths(appname)
|
||||
|
||||
service = kwargs.get('service')
|
||||
if service:
|
||||
config.read_service(service, config_paths)
|
||||
else:
|
||||
shell = kwargs.get('shell', False)
|
||||
for paths in config_paths:
|
||||
config.read(paths, recurse=not shell)
|
||||
if config.getboolean('edbob', 'configure_logging', default=True):
|
||||
config.configure_logging()
|
||||
shell = bool(kwargs.get('shell'))
|
||||
for paths in config_paths:
|
||||
config.read(paths, recurse=not shell)
|
||||
config.configure_logging()
|
||||
|
||||
default_modules = 'edbob.time'
|
||||
modules = config.get('edbob', 'init', default=default_modules)
|
||||
if modules:
|
||||
modules = modules.split(',')
|
||||
init_modules(modules, config)
|
||||
|
||||
edbob.graft(edbob, locals(), 'config')
|
||||
inited.append('edbob')
|
||||
|
||||
|
||||
def init_modules(names, config=None):
|
||||
"""
|
||||
Initialize the given modules. ``names`` should be a sequence of strings,
|
||||
each of which should be a dotted module name. If ``config`` is not
|
||||
specified, :attr:`edbob.config` is assumed.
|
||||
"""
|
||||
|
||||
if config is None:
|
||||
config = edbob.config
|
||||
|
||||
for name in names:
|
||||
name = name.strip()
|
||||
if name not in inited:
|
||||
for name in modules.split(','):
|
||||
name = name.strip()
|
||||
module = __import__(name, globals(), locals(), fromlist=['init'])
|
||||
if not hasattr(module, 'init'):
|
||||
raise InitError(module)
|
||||
getattr(module, 'init')(config)
|
||||
inited.append(name)
|
||||
# config.inited.append(name)
|
||||
|
||||
# config.inited.append('edbob')
|
||||
edbob.graft(edbob, locals(), 'config')
|
||||
edbob.inited = True
|
||||
|
|
|
@ -82,9 +82,6 @@ def load_spec(spec):
|
|||
necessary.
|
||||
"""
|
||||
|
||||
if spec.count(':') != 1:
|
||||
raise exceptions.InvalidSpec(spec)
|
||||
|
||||
module_path, obj = spec.split(':')
|
||||
module = import_module_path(module_path)
|
||||
try:
|
||||
|
|
|
@ -29,8 +29,6 @@
|
|||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from zope.sqlalchemy import ZopeTransactionExtension
|
||||
|
||||
import edbob
|
||||
|
||||
|
||||
__all__ = ['Session']
|
||||
|
||||
|
@ -58,7 +56,6 @@ def includeme(config):
|
|||
config.include('pyramid_tm')
|
||||
|
||||
# Configure SQLAlchemy session.
|
||||
Session.configure(bind=edbob.engine)
|
||||
Session.configure(extension=ZopeTransactionExtension())
|
||||
|
||||
# Configure user authentication / authorization.
|
||||
|
@ -72,6 +69,3 @@ def includeme(config):
|
|||
|
||||
# Add static views.
|
||||
config.include('edbob.pyramid.static')
|
||||
|
||||
# Add subscriber hooks.
|
||||
config.include('edbob.pyramid.subscribers')
|
||||
|
|
|
@ -50,8 +50,7 @@ from edbob.pyramid.forms.formalchemy.renderers import *
|
|||
__all__ = ['ChildGridField', 'PropertyField', 'EnumFieldRenderer',
|
||||
'PrettyDateTimeFieldRenderer', 'AutocompleteFieldRenderer',
|
||||
'FieldSet', 'make_fieldset', 'required', 'pretty_datetime',
|
||||
'AssociationProxyField', 'StrippingFieldRenderer',
|
||||
'YesNoFieldRenderer']
|
||||
'AssociationProxyField', 'YesNoFieldRenderer']
|
||||
|
||||
|
||||
class TemplateEngine(formalchemy.templates.TemplateEngine):
|
||||
|
@ -185,6 +184,7 @@ class DateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
|
|||
if isinstance(value, datetime.datetime):
|
||||
value = edbob.local_time(value)
|
||||
return value.strftime(self.format)
|
||||
print type(value)
|
||||
return ''
|
||||
|
||||
FieldSet.default_renderers[formalchemy.types.DateTime] = DateTimeFieldRenderer
|
||||
|
|
|
@ -32,7 +32,7 @@ from pyramid.renderers import render
|
|||
|
||||
|
||||
__all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer',
|
||||
'StrippingFieldRenderer', 'YesNoFieldRenderer']
|
||||
'YesNoFieldRenderer']
|
||||
|
||||
|
||||
def AutocompleteFieldRenderer(service_url, field_value=None, field_display=None, width='300px'):
|
||||
|
@ -74,26 +74,17 @@ def EnumFieldRenderer(enum):
|
|||
return ''
|
||||
if value in enum:
|
||||
return enum[value]
|
||||
return str(value)
|
||||
return value
|
||||
|
||||
def render(self, **kwargs):
|
||||
opts = [(enum[x], x) for x in sorted(enum)]
|
||||
opts = []
|
||||
for value in sorted(enum):
|
||||
opts.append((enum[value], value))
|
||||
return formalchemy.fields.SelectFieldRenderer.render(self, opts, **kwargs)
|
||||
|
||||
return Renderer
|
||||
|
||||
|
||||
class StrippingFieldRenderer(formalchemy.TextFieldRenderer):
|
||||
"""
|
||||
Standard text field renderer, which strips whitespace from either end of
|
||||
the input value on deserialization.
|
||||
"""
|
||||
|
||||
def deserialize(self):
|
||||
value = super(StrippingFieldRenderer, self).deserialize()
|
||||
return value.strip()
|
||||
|
||||
|
||||
class YesNoFieldRenderer(formalchemy.fields.CheckBoxFieldRenderer):
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
|
|
|
@ -26,29 +26,15 @@
|
|||
``edbob.pyramid.forms.simpleform`` -- pyramid_simpleform Forms
|
||||
"""
|
||||
|
||||
from pyramid.renderers import render
|
||||
|
||||
import formencode
|
||||
import pyramid_simpleform
|
||||
from pyramid.renderers import render
|
||||
from pyramid_simpleform.renderers import FormRenderer
|
||||
|
||||
from edbob.pyramid import helpers
|
||||
from edbob.pyramid.forms import Form
|
||||
|
||||
|
||||
__all__ = ['Schema', 'SimpleForm']
|
||||
|
||||
|
||||
class Schema(formencode.Schema):
|
||||
"""
|
||||
Subclass of ``formencode.Schema``, which exists only to ignore extra
|
||||
fields. These normally would cause a schema instance to be deemed invalid,
|
||||
and pretty much *every* form has a submit button which would be considered
|
||||
an extra field.
|
||||
"""
|
||||
|
||||
allow_extra_fields = True
|
||||
filter_extra_fields = True
|
||||
__all__ = ['SimpleForm']
|
||||
|
||||
|
||||
class SimpleForm(Form):
|
||||
|
|
|
@ -54,9 +54,6 @@ class AlchemyGrid(Grid):
|
|||
self._formalchemy_grid.prettify = prettify
|
||||
self.noclick_fields = []
|
||||
|
||||
def __delattr__(self, attr):
|
||||
delattr(self._formalchemy_grid, attr)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self._formalchemy_grid, attr)
|
||||
|
||||
|
|
0
edbob/pyramid/handlers/__init__.py
Normal file
0
edbob/pyramid/handlers/__init__.py
Normal file
291
edbob/pyramid/handlers/base.py
Normal file
291
edbob/pyramid/handlers/base.py
Normal file
|
@ -0,0 +1,291 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.pyramid.handlers.base`` -- Base Handlers
|
||||
"""
|
||||
|
||||
from pyramid.renderers import render_to_response
|
||||
from pyramid.httpexceptions import HTTPException, HTTPFound, HTTPOk, HTTPUnauthorized
|
||||
|
||||
# import sqlahelper
|
||||
|
||||
# # import rattail.pyramid.forms.util as util
|
||||
# from rattail.db.perms import has_permission
|
||||
# from rattail.pyramid.forms.formalchemy import Grid
|
||||
|
||||
|
||||
class needs_perm(object):
|
||||
"""
|
||||
Decorator to be used for handler methods which should restrict access based
|
||||
on the current user's permissions.
|
||||
"""
|
||||
|
||||
def __init__(self, permission, **kwargs):
|
||||
self.permission = permission
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __call__(self, fn):
|
||||
permission = self.permission
|
||||
kw = self.kwargs
|
||||
def wrapped(self):
|
||||
if not self.request.current_user:
|
||||
self.request.session['referrer'] = self.request.url_generator.current()
|
||||
self.request.session.flash("You must be logged in to do that.", 'error')
|
||||
return HTTPFound(location=self.request.route_url('login'))
|
||||
if not has_permission(self.request.current_user, permission):
|
||||
self.request.session.flash("You do not have permission to do that.", 'error')
|
||||
home = kw.get('redirect', self.request.route_url('home'))
|
||||
return HTTPFound(location=home)
|
||||
return fn(self)
|
||||
return wrapped
|
||||
|
||||
|
||||
def needs_user(fn):
|
||||
"""
|
||||
Decorator for handler methods which require simply that a user be currently
|
||||
logged in.
|
||||
"""
|
||||
|
||||
def wrapped(self):
|
||||
if not self.request.current_user:
|
||||
self.request.session['referrer'] = self.request.url_generator.current()
|
||||
self.request.session.flash("You must be logged in to do that.", 'error')
|
||||
return HTTPFound(location=self.request.route_url('login'))
|
||||
return fn(self)
|
||||
return wrapped
|
||||
|
||||
|
||||
class Handler(object):
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
self.Session = sqlahelper.get_session()
|
||||
|
||||
# def json_response(self, data={}):
|
||||
# response = render_to_response('json', data, request=self.request)
|
||||
# response.headers['Content-Type'] = 'application/json'
|
||||
# return response
|
||||
|
||||
|
||||
class CrudHandler(Handler):
|
||||
# """
|
||||
# This handler provides all the goodies typically associated with general
|
||||
# CRUD functionality, e.g. search filters and grids.
|
||||
# """
|
||||
|
||||
def crud(self, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None):
|
||||
"""
|
||||
Adds a common CRUD mechanism for objects.
|
||||
|
||||
``cls`` should be a SQLAlchemy-mapped class, presumably deriving from
|
||||
:class:`rattail.Object`.
|
||||
|
||||
``fieldset_factory`` must be a callable which accepts the fieldset's
|
||||
"model" as its only positional argument.
|
||||
|
||||
``home`` will be used as the redirect location once a form is fully
|
||||
validated and data saved. If you do not speficy this parameter, the
|
||||
user will be redirected to be the CRUD page for the new object (e.g. so
|
||||
an object may be created before certain properties may be edited).
|
||||
|
||||
``delete`` may either be a string containing a URL to which the user
|
||||
should be redirected after the object has been deleted, or else a
|
||||
callback which will be executed *instead of* the normal algorithm
|
||||
(which is merely to delete the object via the Session).
|
||||
|
||||
``post_sync`` may be a callback which will be executed immediately
|
||||
after ``FieldSet.sync()`` is called, i.e. after validation as well.
|
||||
|
||||
``pre_render`` may be a callback which will be executed after any POST
|
||||
processing has occured, but just before rendering.
|
||||
"""
|
||||
|
||||
uuid = self.request.params.get('uuid')
|
||||
obj = self.Session.query(cls).get(uuid) if uuid else cls
|
||||
assert obj
|
||||
|
||||
if self.request.params.get('delete'):
|
||||
if delete:
|
||||
if isinstance(delete, basestring):
|
||||
self.Session.delete(obj)
|
||||
return HTTPFound(location=delete)
|
||||
res = delete(obj)
|
||||
if res:
|
||||
return res
|
||||
else:
|
||||
self.Session.delete(obj)
|
||||
if not home:
|
||||
raise ValueError("Must specify 'home' or 'delete' url "
|
||||
"in call to CrudHandler.crud()")
|
||||
return HTTPFound(location=home)
|
||||
|
||||
fs = fieldset_factory(obj)
|
||||
|
||||
# if not fs.readonly and self.request.params.get('fieldset'):
|
||||
# fs.rebind(data=self.request.params)
|
||||
# if fs.validate():
|
||||
# fs.sync()
|
||||
# if post_sync:
|
||||
# res = post_sync(fs)
|
||||
# if isinstance(res, HTTPFound):
|
||||
# return res
|
||||
# if self.request.params.get('partial'):
|
||||
# self.Session.flush()
|
||||
# return self.json_success(uuid=fs.model.uuid)
|
||||
# return HTTPFound(location=self.request.route_url(objects, action='index'))
|
||||
|
||||
if not fs.readonly and self.request.POST:
|
||||
# print self.request.POST
|
||||
fs.rebind(data=self.request.params)
|
||||
if fs.validate():
|
||||
fs.sync()
|
||||
if post_sync:
|
||||
res = post_sync(fs)
|
||||
if res:
|
||||
return res
|
||||
if self.request.params.get('partial'):
|
||||
self.Session.flush()
|
||||
return self.json_success(uuid=fs.model.uuid)
|
||||
|
||||
if not home:
|
||||
self.Session.flush()
|
||||
home = self.request.url_generator.current() + '?uuid=' + fs.model.uuid
|
||||
self.request.session.flash("%s \"%s\" has been %s." % (
|
||||
fs.crud_title, fs.get_display_text(),
|
||||
'updated' if fs.edit else 'created'))
|
||||
return HTTPFound(location=home)
|
||||
|
||||
data = {'fieldset': fs, 'crud': True}
|
||||
|
||||
if pre_render:
|
||||
res = pre_render(fs)
|
||||
if res:
|
||||
if isinstance(res, HTTPException):
|
||||
return res
|
||||
data.update(res)
|
||||
|
||||
# data = {'fieldset':fs}
|
||||
# if self.request.params.get('partial'):
|
||||
# return render_to_response('/%s/crud_partial.mako' % objects,
|
||||
# data, request=self.request)
|
||||
# return data
|
||||
|
||||
return data
|
||||
|
||||
def grid(self, *args, **kwargs):
|
||||
"""
|
||||
Convenience function which returns a grid. The only functionality this
|
||||
method adds is the ``session`` parameter.
|
||||
"""
|
||||
|
||||
return Grid(session=self.Session(), *args, **kwargs)
|
||||
|
||||
# def get_grid(self, name, grid, query, search=None, url=None, **defaults):
|
||||
# """
|
||||
# Convenience function for obtaining the configuration for a grid,
|
||||
# and then obtaining the grid itself.
|
||||
|
||||
# ``name`` is essentially the config key, e.g. ``'products.lookup'``, and
|
||||
# in fact is expected to take that precise form (where the first part is
|
||||
# considered the handler name and the second part the action name).
|
||||
|
||||
# ``grid`` must be a callable with a signature of ``grid(query,
|
||||
# config)``, and ``query`` will be passed directly to the ``grid``
|
||||
# callable. ``search`` will be used to inform the grid of the search in
|
||||
# effect, if any. ``defaults`` will be used to customize the grid config.
|
||||
# """
|
||||
|
||||
# if not url:
|
||||
# handler, action = name.split('.')
|
||||
# url = self.request.route_url(handler, action=action)
|
||||
# config = util.get_grid_config(name, self.request, search,
|
||||
# url=url, **defaults)
|
||||
# return grid(query, config)
|
||||
|
||||
# def get_search_form(self, name, labels={}, **defaults):
|
||||
# """
|
||||
# Convenience function for obtaining the configuration for a search form,
|
||||
# and then obtaining the form itself.
|
||||
|
||||
# ``name`` is essentially the config key, e.g. ``'products.lookup'``.
|
||||
# The ``labels`` dictionary can be used to override the default labels
|
||||
# displayed for the various search fields. The ``defaults`` dictionary
|
||||
# is used to customize the search config.
|
||||
# """
|
||||
|
||||
# config = util.get_search_config(name, self.request,
|
||||
# self.filter_map(), **defaults)
|
||||
# form = util.get_search_form(config, **labels)
|
||||
# return form
|
||||
|
||||
# def object_crud(self, cls, objects=None, post_sync=None):
|
||||
# """
|
||||
# This method is a desperate attempt to encapsulate shared CRUD logic
|
||||
# which is useful across all editable data objects.
|
||||
|
||||
# ``objects``, if provided, should be the plural name for the class as
|
||||
# used in internal naming, e.g. ``'products'``. A default will be used
|
||||
# if you do not provide this value.
|
||||
|
||||
# ``post_sync``, if provided, should be a callable which accepts a
|
||||
# ``formalchemy.Fieldset`` instance as its only argument. It will be
|
||||
# called immediately after the fieldset is synced.
|
||||
# """
|
||||
|
||||
# if not objects:
|
||||
# objects = cls.__name__.lower() + 's'
|
||||
|
||||
# uuid = self.request.params.get('uuid')
|
||||
# obj = self.Session.query(cls).get(uuid) if uuid else cls
|
||||
# assert obj
|
||||
|
||||
# fs = self.fieldset(obj)
|
||||
|
||||
# if not fs.readonly and self.request.params.get('fieldset'):
|
||||
# fs.rebind(data=self.request.params)
|
||||
# if fs.validate():
|
||||
# fs.sync()
|
||||
# if post_sync:
|
||||
# res = post_sync(fs)
|
||||
# if isinstance(res, HTTPFound):
|
||||
# return res
|
||||
# if self.request.params.get('partial'):
|
||||
# self.Session.flush()
|
||||
# return self.json_success(uuid=fs.model.uuid)
|
||||
# return HTTPFound(location=self.request.route_url(objects, action='index'))
|
||||
|
||||
# data = {'fieldset':fs}
|
||||
# if self.request.params.get('partial'):
|
||||
# return render_to_response('/%s/crud_partial.mako' % objects,
|
||||
# data, request=self.request)
|
||||
# return data
|
||||
|
||||
# def render_grid(self, grid, search=None, **kwargs):
|
||||
# """
|
||||
# Convenience function to render a standard grid. Really just calls
|
||||
# :func:`dtail.forms.util.render_grid()`.
|
||||
# """
|
||||
|
||||
# return util.render_grid(self.request, grid, search, **kwargs)
|
72
edbob/pyramid/handlers/util.py
Normal file
72
edbob/pyramid/handlers/util.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.pyramid.handlers.util`` -- Handler Utilities
|
||||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
from edbob.db.perms import has_permission
|
||||
|
||||
|
||||
class needs_perm(object):
|
||||
"""
|
||||
Decorator to be used for handler methods which should restrict access based
|
||||
on the current user's permissions.
|
||||
"""
|
||||
|
||||
def __init__(self, permission, **kwargs):
|
||||
self.permission = permission
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __call__(self, fn):
|
||||
permission = self.permission
|
||||
kw = self.kwargs
|
||||
def wrapped(self):
|
||||
if not self.request.current_user:
|
||||
self.request.session['referrer'] = self.request.url_generator.current()
|
||||
self.request.session.flash("You must be logged in to do that.", 'error')
|
||||
return HTTPFound(location=self.request.route_url('login'))
|
||||
if not has_permission(self.request.current_user, permission):
|
||||
self.request.session.flash("You do not have permission to do that.", 'error')
|
||||
home = kw.get('redirect', self.request.route_url('home'))
|
||||
return HTTPFound(location=home)
|
||||
return fn(self)
|
||||
return wrapped
|
||||
|
||||
|
||||
def needs_user(fn):
|
||||
"""
|
||||
Decorator for handler methods which require simply that a user be currently
|
||||
logged in.
|
||||
"""
|
||||
|
||||
def wrapped(self):
|
||||
if not self.request.current_user:
|
||||
self.request.session['referrer'] = self.request.url_generator.current()
|
||||
self.request.session.flash("You must be logged in to do that.", 'error')
|
||||
return HTTPFound(location=self.request.route_url('login'))
|
||||
return fn(self)
|
||||
return wrapped
|
|
@ -47,32 +47,50 @@ class SessionProgress(object):
|
|||
|
||||
def __init__(self, session, key):
|
||||
self.session = get_progress_session(session, key)
|
||||
self.canceled = False
|
||||
self.clear()
|
||||
self.cancelled = False
|
||||
|
||||
def __call__(self, message, maximum):
|
||||
self.clear()
|
||||
self.session['complete'] = False
|
||||
self.session['message'] = message
|
||||
self.session['maximum'] = maximum
|
||||
self.session['cancelled'] = False
|
||||
self.session['value'] = 0
|
||||
self.session.save()
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
self.session.clear()
|
||||
self.session['complete'] = False
|
||||
self.session['error'] = False
|
||||
self.session['canceled'] = False
|
||||
self.session.save()
|
||||
|
||||
def update(self, value):
|
||||
self.session.load()
|
||||
if self.session.get('canceled'):
|
||||
self.canceled = True
|
||||
if self.session.get('cancelled'):
|
||||
self.cancelled = True
|
||||
else:
|
||||
self.session['value'] = value
|
||||
self.session.save()
|
||||
return not self.canceled
|
||||
return not self.cancelled
|
||||
|
||||
def destroy(self):
|
||||
if not self.cancelled:
|
||||
self.session['complete'] = True
|
||||
self.session.save()
|
||||
|
||||
def secondary_progress(self):
|
||||
return SecondarySessionProgress(self)
|
||||
|
||||
|
||||
class SecondarySessionProgress(object):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.session = parent.session
|
||||
|
||||
def __call__(self, message, maximum):
|
||||
self.session['message'] = message
|
||||
self.session['value'] = 0
|
||||
self.session['maximum'] = maximum
|
||||
self.session.save()
|
||||
return self
|
||||
|
||||
def update(self, value):
|
||||
return self.parent.update(value)
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
|
|
@ -87,12 +87,81 @@ div.error {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul.error {
|
||||
color: #dd6666;
|
||||
font-weight: bold;
|
||||
padding: 0px;
|
||||
/* td.right { */
|
||||
/* float: none; */
|
||||
/* } */
|
||||
|
||||
/* table.wrapper td.right { */
|
||||
/* vertical-align: bottom; */
|
||||
/* } */
|
||||
|
||||
|
||||
/******************************
|
||||
* Main Layout
|
||||
******************************/
|
||||
|
||||
html, body, #container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ul.error li {
|
||||
list-style-type: none;
|
||||
body > #container {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
width: 1000px;
|
||||
}
|
||||
|
||||
#header {
|
||||
border-bottom: 1px solid #000000;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#body {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 5em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
margin-top: -4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
* Header
|
||||
******************************/
|
||||
|
||||
#header h1 {
|
||||
margin: 0px 5px 10px 5px;
|
||||
}
|
||||
|
||||
#login {
|
||||
margin: 8px 20px auto auto;
|
||||
}
|
||||
|
||||
#user-menu {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#home-link {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#header-links {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#main-menu {
|
||||
border-top: 1px solid black;
|
||||
clear: both;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#main-menu li {
|
||||
display: inline;
|
||||
margin-right: 15px;
|
||||
}
|
|
@ -38,18 +38,11 @@ div.fieldset {
|
|||
|
||||
div.field-wrapper {
|
||||
clear: both;
|
||||
min-height: 30px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
div.field-wrapper.error {
|
||||
background-color: #ddcccc;
|
||||
border: 2px solid #dd6666;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
div.field-wrapper label {
|
||||
color: #000000;
|
||||
display: block;
|
||||
float: left;
|
||||
width: 140px;
|
||||
|
@ -78,15 +71,6 @@ div.field-wrapper div.field textarea {
|
|||
width: 320px;
|
||||
}
|
||||
|
||||
label input[type=checkbox] {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
* Buttons
|
||||
******************************/
|
||||
|
||||
div.buttons {
|
||||
clear: both;
|
||||
margin-top: 10px;
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
|
||||
/******************************
|
||||
* Main Layout
|
||||
******************************/
|
||||
|
||||
html, body, #container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body > #container {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
width: 1000px;
|
||||
}
|
||||
|
||||
#header {
|
||||
border-bottom: 1px solid #000000;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#body {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 5em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
clear: both;
|
||||
margin-top: -4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
* Header
|
||||
******************************/
|
||||
|
||||
#header h1 {
|
||||
margin: 0px 5px 10px 5px;
|
||||
}
|
||||
|
||||
#login {
|
||||
margin: 8px 20px auto auto;
|
||||
}
|
||||
|
||||
#login a.username {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#user-menu {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#home-link {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#header-links {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#main-menu {
|
||||
border-top: 1px solid black;
|
||||
clear: both;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#main-menu li {
|
||||
display: inline;
|
||||
margin-right: 15px;
|
||||
}
|
|
@ -1,33 +1,17 @@
|
|||
|
||||
/******************************
|
||||
* Permission Lists
|
||||
* perms.css
|
||||
******************************/
|
||||
|
||||
div.field-wrapper.permissions div.field div.group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
div.field-wrapper.permissions div.field div.group p {
|
||||
div.field-couple.permissions div.field p.group {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.field-wrapper.permissions div.field label {
|
||||
div.field-couple.permissions div.field label {
|
||||
float: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
div.field-wrapper.permissions div.field label input {
|
||||
margin-left: 15px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
div.field-wrapper.permissions div.field div.group p.perm {
|
||||
font-weight: normal;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
div.field-wrapper.permissions div.field div.group p.perm span {
|
||||
font-family: monospace;
|
||||
/* font-weight: bold; */
|
||||
div.field-couple.permissions div.field label input {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
|
@ -76,16 +76,11 @@ def context_found(event):
|
|||
request.user = Session.query(edbob.User).get(uuid)
|
||||
|
||||
def has_perm(perm):
|
||||
return has_permission(request.user, perm, session=Session())
|
||||
if not request.user:
|
||||
return False
|
||||
return has_permission(request.user, perm)
|
||||
request.has_perm = has_perm
|
||||
|
||||
def has_any_perm(perms):
|
||||
for perm in perms:
|
||||
if has_permission(request.user, perm, session=Session()):
|
||||
return True
|
||||
return False
|
||||
request.has_any_perm = has_any_perm
|
||||
|
||||
def get_referrer(default=None):
|
||||
if request.params.get('referrer'):
|
||||
return request.params['referrer']
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<%inherit file="/base.mako" />
|
||||
|
||||
<%def name="title()">Change Password</%def>
|
||||
|
||||
<div class="form">
|
||||
${h.form(url('change_password'))}
|
||||
${form.referrer_field()}
|
||||
${form.field_div('current_password', form.password('current_password'))}
|
||||
${form.field_div('new_password', form.password('new_password'))}
|
||||
${form.field_div('confirm_password', form.password('confirm_password'))}
|
||||
<div class="buttons">
|
||||
${h.submit('submit', "Change Password")}
|
||||
</div>
|
||||
${h.end_form()}
|
||||
</div>
|
|
@ -1,18 +1,3 @@
|
|||
<%inherit file="/form.mako" />
|
||||
|
||||
<%def name="title()">${"New "+form.pretty_name if form.creating else form.pretty_name+' : '+h.literal(str(form.fieldset.model))}</%def>
|
||||
|
||||
<%def name="head_tags()">
|
||||
${parent.head_tags()}
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('a.delete').click(function() {
|
||||
if (! confirm("Do you really wish to delete this object?")) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
<%inherit file="/edbob/crud.mako" />
|
||||
|
||||
${parent.body()}
|
||||
|
|
|
@ -18,8 +18,7 @@
|
|||
${h.javascript_link(request.static_url('edbob.pyramid:static/js/jquery.autocomplete.js'))}
|
||||
${h.javascript_link(request.static_url('edbob.pyramid:static/js/edbob.js'))}
|
||||
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/base.css'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/layout.css'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/edbob.css'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/grids.css'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/filters.css'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/forms.css'))}
|
||||
|
@ -38,7 +37,7 @@
|
|||
<h1 class="left">${self.title()}</h1>
|
||||
<div id="login" class="right">
|
||||
% if request.user:
|
||||
${h.link_to(request.user.display_name, url('change_password'), class_='username')}
|
||||
logged in as <strong>${request.user.display_name}</strong>
|
||||
(${h.link_to("logout", url('logout'))})
|
||||
% else:
|
||||
${h.link_to("login", url('login'))}
|
5
edbob/pyramid/templates/edbob/crud.mako
Normal file
5
edbob/pyramid/templates/edbob/crud.mako
Normal file
|
@ -0,0 +1,5 @@
|
|||
<%inherit file="/form.mako" />
|
||||
|
||||
<%def name="title()">${"New "+form.pretty_name if form.creating else form.pretty_name+' : '+str(form.fieldset.model)}</%def>
|
||||
|
||||
${parent.body()}
|
|
@ -1,7 +1,7 @@
|
|||
<div class="fieldset">
|
||||
% for field in fieldset.render_fields.itervalues():
|
||||
% if field.requires_label:
|
||||
<div class="field-wrapper ${field.name}">
|
||||
<div class="field-wrapper">
|
||||
${field.label_tag()|n}
|
||||
<div class="field">
|
||||
${field.render_readonly()}
|
||||
|
|
2
edbob/pyramid/templates/people/base.mako
Normal file
2
edbob/pyramid/templates/people/base.mako
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%inherit file="/base.mako" />
|
||||
${parent.body()}
|
|
@ -1,28 +0,0 @@
|
|||
<%inherit file="/crud.mako" />
|
||||
|
||||
<%def name="crud_name()">Person</%def>
|
||||
|
||||
<%def name="context_menu_items()">
|
||||
<li>${h.link_to("Back to People", url('people'))}</li>
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
||||
## % if fieldset.edit:
|
||||
## <h2>User Info</h2>
|
||||
## % if user:
|
||||
## ${user.render()|n}
|
||||
## <div class="buttons">
|
||||
## <button type="button" onclick="location.href = '${url('user.edit', uuid=user.model.uuid)}';">Edit User</button>
|
||||
## </div>
|
||||
## % else:
|
||||
## <p>This person does not have a user account.</p>
|
||||
## ${h.form(url('user.new'))}
|
||||
## ${h.hidden('User--person_uuid', value=fieldset.model.uuid)}
|
||||
## ${h.hidden('User--username')}
|
||||
## <div class="buttons">
|
||||
## ${h.submit('submit', "Create User")}
|
||||
## </div>
|
||||
## ${h.end_form()}
|
||||
## % endif
|
||||
## % endif
|
|
@ -1,11 +1,12 @@
|
|||
<%inherit file="/grid.mako" />
|
||||
<%inherit file="/people/base.mako" />
|
||||
<%inherit file="/index.mako" />
|
||||
|
||||
<%def name="title()">People</%def>
|
||||
|
||||
<%def name="context_menu_items()">
|
||||
## % if request.has_perm('people.create'):
|
||||
## <li>${h.link_to("Create a new Person", url('person.new'))}</li>
|
||||
## % endif
|
||||
% if request.has_perm('people.create'):
|
||||
<li>${h.link_to("Create a new Person", url('person.new'))}</li>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
|
29
edbob/pyramid/templates/people/person.mako
Normal file
29
edbob/pyramid/templates/people/person.mako
Normal file
|
@ -0,0 +1,29 @@
|
|||
<%inherit file="/people/base.mako" />
|
||||
<%inherit file="/crud.mako" />
|
||||
|
||||
<%def name="crud_name()">Person</%def>
|
||||
|
||||
<%def name="menu()">
|
||||
<p>${h.link_to("Back to People", url('people.list'))}</p>
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
||||
% if fieldset.edit:
|
||||
<h2>User Info</h2>
|
||||
% if user:
|
||||
${user.render()|n}
|
||||
<div class="buttons">
|
||||
<button type="button" onclick="location.href = '${url('user.edit', uuid=user.model.uuid)}';">Edit User</button>
|
||||
</div>
|
||||
% else:
|
||||
<p>This person does not have a user account.</p>
|
||||
${h.form(url('user.new'))}
|
||||
${h.hidden('User--person_uuid', value=fieldset.model.uuid)}
|
||||
${h.hidden('User--username')}
|
||||
<div class="buttons">
|
||||
${h.submit('submit', "Create User")}
|
||||
</div>
|
||||
${h.end_form()}
|
||||
% endif
|
||||
% endif
|
|
@ -5,8 +5,7 @@
|
|||
<title>Working...</title>
|
||||
${h.javascript_link(request.static_url('edbob.pyramid:static/js/jquery.js'))}
|
||||
${h.javascript_link(request.static_url('edbob.pyramid:static/js/edbob.js'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/base.css'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/layout.css'))}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/edbob.css'))}
|
||||
<style type="text/css">
|
||||
|
||||
#container {
|
||||
|
@ -62,29 +61,25 @@
|
|||
$.ajax({
|
||||
url: '${url('progress', key=key)}',
|
||||
success: function(data) {
|
||||
if (data.complete || data.maximum) {
|
||||
$('#message').html(data.message);
|
||||
$('#total').html('('+data.maximum+' total)');
|
||||
$('#cancel button').show();
|
||||
if (data.complete) {
|
||||
clearInterval(updater);
|
||||
$('#cancel button').hide();
|
||||
$('#total').html('done!');
|
||||
$('#complete').css('width', '100%');
|
||||
$('#remaining').hide();
|
||||
$('#percentage').html('100%');
|
||||
location.href = data.success_url;
|
||||
} else {
|
||||
var width = parseInt(data.value) / parseInt(data.maximum);
|
||||
width = Math.round(100 * width);
|
||||
if (width > 0) {
|
||||
$('#complete').css('width', width+'%');
|
||||
$('#remaining').css('width', 'auto');
|
||||
}
|
||||
$('#percentage').html(width+'%');
|
||||
$('#message').html(data.message);
|
||||
$('#total').html('('+data.maximum+' total)');
|
||||
$('#cancel button').show();
|
||||
if (data.complete) {
|
||||
clearInterval(updater);
|
||||
$('#cancel button').hide();
|
||||
$('#total').html('done!');
|
||||
$('#complete').css('width', '100%');
|
||||
$('#remaining').hide();
|
||||
$('#percentage').html('100%');
|
||||
location.href = data.success_url;
|
||||
} else {
|
||||
var width = parseInt(data.value) / parseInt(data.maximum);
|
||||
width = Math.round(100 * width);
|
||||
if (width > 0) {
|
||||
$('#complete').css('width', width+'%');
|
||||
$('#remaining').css('width', 'auto');
|
||||
}
|
||||
} else if (data.error) {
|
||||
location.href = '${cancel_url}';
|
||||
$('#percentage').html(width+'%');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -117,7 +112,7 @@
|
|||
|
||||
<div id="wrapper">
|
||||
|
||||
<p><span id="message">${initial_msg or "Working"} (please wait)</span> ... <span id="total"></span></p>
|
||||
<p><span id="message">Working</span> ... <span id="total"></span></p>
|
||||
|
||||
<table id="progress-wrapper">
|
||||
<tr>
|
||||
|
|
2
edbob/pyramid/templates/roles/base.mako
Normal file
2
edbob/pyramid/templates/roles/base.mako
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%inherit file="/base.mako" />
|
||||
${parent.body()}
|
|
@ -1,18 +0,0 @@
|
|||
<%inherit file="edbob.pyramid:templates/crud.mako" />
|
||||
|
||||
<%def name="head_tags()">
|
||||
${parent.head_tags()}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/perms.css'))}
|
||||
</%def>
|
||||
|
||||
<%def name="context_menu_items()">
|
||||
<li>${h.link_to("Back to Roles", url('roles'))}</li>
|
||||
% if form.readonly:
|
||||
<li>${h.link_to("Edit this Role", url('role.update', uuid=form.fieldset.model.uuid))}</li>
|
||||
% elif form.updating:
|
||||
<li>${h.link_to("View this Role", url('role.read', uuid=form.fieldset.model.uuid))}</li>
|
||||
% endif
|
||||
<li>${h.link_to("Delete this Role", url('role.delete', uuid=form.fieldset.model.uuid), class_='delete')}</li>
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
|
@ -1,11 +1,10 @@
|
|||
<%inherit file="/grid.mako" />
|
||||
<%inherit file="/roles/base.mako" />
|
||||
<%inherit file="/index.mako" />
|
||||
|
||||
<%def name="title()">Roles</%def>
|
||||
|
||||
<%def name="context_menu_items()">
|
||||
% if request.has_perm('roles.create'):
|
||||
<li>${h.link_to("Create a new Role", url('role.create'))}</li>
|
||||
% endif
|
||||
<li>${h.link_to("Create a new Role", url('role.new'))}</li>
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
|
15
edbob/pyramid/templates/roles/role.mako
Normal file
15
edbob/pyramid/templates/roles/role.mako
Normal file
|
@ -0,0 +1,15 @@
|
|||
<%inherit file="/roles/base.mako" />
|
||||
<%inherit file="/crud.mako" />
|
||||
|
||||
<%def name="crud_name()">Role</%def>
|
||||
|
||||
<%def name="head_tags()">
|
||||
${parent.head_tags()}
|
||||
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/perms.css'))}
|
||||
</%def>
|
||||
|
||||
<%def name="menu()">
|
||||
<p>${h.link_to("Back to Roles", url('roles.list'))}</p>
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
2
edbob/pyramid/templates/users/base.mako
Normal file
2
edbob/pyramid/templates/users/base.mako
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%inherit file="/base.mako" />
|
||||
${parent.body()}
|
|
@ -1,12 +1,10 @@
|
|||
<%inherit file="/users/base.mako" />
|
||||
<%inherit file="/crud.mako" />
|
||||
|
||||
<%def name="crud_name()">User</%def>
|
||||
|
||||
<%def name="context_menu_items()">
|
||||
<li>${h.link_to("Back to Users", url('users'))}</li>
|
||||
% if form.readonly:
|
||||
<li>${h.link_to("Edit this User", url('user.update', uuid=form.fieldset.model.uuid))}</li>
|
||||
% elif form.updating:
|
||||
<li>${h.link_to("View this User", url('user.read', uuid=form.fieldset.model.uuid))}</li>
|
||||
% endif
|
||||
<li>${h.link_to("Back to Users", url('users.list'))}</li>
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
|
2
edbob/pyramid/templates/users/edit.mako
Normal file
2
edbob/pyramid/templates/users/edit.mako
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%inherit file="/users/crud.mako" />
|
||||
${parent.body()}
|
|
@ -1,11 +1,10 @@
|
|||
<%inherit file="/grid.mako" />
|
||||
<%inherit file="/users/base.mako" />
|
||||
<%inherit file="/index.mako" />
|
||||
|
||||
<%def name="title()">Users</%def>
|
||||
|
||||
<%def name="context_menu_items()">
|
||||
% if request.has_perm('users.create'):
|
||||
<li>${h.link_to("Create a new User", url('user.create'))}</li>
|
||||
% endif
|
||||
<li>${h.link_to("Create a new User", url('user.new'))}</li>
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
|
2
edbob/pyramid/templates/users/new.mako
Normal file
2
edbob/pyramid/templates/users/new.mako
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%inherit file="/users/crud.mako" />
|
||||
${parent.body()}
|
|
@ -1,59 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.pyramid.tweens`` -- Tween Factories
|
||||
"""
|
||||
|
||||
import sqlalchemy.exc
|
||||
|
||||
from transaction.interfaces import TransientError
|
||||
|
||||
|
||||
def sqlerror_tween_factory(handler, registry):
|
||||
"""
|
||||
Produces a tween which will convert ``sqlalchemy.exc.OperationalError``
|
||||
instances (caused by database server restart) into a retryable
|
||||
``transaction.interfaces.TransientError`` instance, so that a second
|
||||
attempt may be made to connect to the database before really giving up.
|
||||
|
||||
.. note::
|
||||
This tween alone is not enough to cause the transaction to be retried;
|
||||
it only marks the error as being *retryable*. If you wish more than one
|
||||
attempt to be made, you must define the ``tm.attempts`` setting within
|
||||
your Pyramid app configuration. See `Retrying
|
||||
<http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/#retrying>`_
|
||||
for more information.
|
||||
"""
|
||||
|
||||
def sqlerror_tween(request):
|
||||
try:
|
||||
response = handler(request)
|
||||
except sqlalchemy.exc.OperationalError, error:
|
||||
if error.connection_invalidated:
|
||||
raise TransientError(str(error))
|
||||
raise
|
||||
return response
|
||||
|
||||
return sqlerror_tween
|
|
@ -26,50 +26,17 @@
|
|||
``edbob.pyramid.views.auth`` -- Auth Views
|
||||
"""
|
||||
|
||||
import formencode
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
from pyramid.security import remember, forget
|
||||
|
||||
import formencode
|
||||
from pyramid_simpleform import Form
|
||||
import pyramid_simpleform.renderers
|
||||
|
||||
from webhelpers.html import tags
|
||||
from webhelpers.html.builder import HTML
|
||||
from pyramid_simpleform.renderers import FormRenderer
|
||||
|
||||
import edbob
|
||||
from edbob.db.auth import authenticate_user, set_user_password
|
||||
from edbob.db.auth import authenticate_user
|
||||
from edbob.pyramid import Session
|
||||
from edbob.util import prettify
|
||||
|
||||
|
||||
class FormRenderer(pyramid_simpleform.renderers.FormRenderer):
|
||||
"""
|
||||
Customized form renderer. Provides some extra methods for convenience.
|
||||
"""
|
||||
|
||||
# Note that as of this writing, this renderer is used only by the
|
||||
# ``change_password`` view. This should probably change, and this class
|
||||
# definition should be moved elsewhere.
|
||||
|
||||
def field_div(self, name, field, label=None):
|
||||
errors = self.errors_for(name)
|
||||
if errors:
|
||||
errors = [HTML.tag('div', class_='field-error', c=x) for x in errors]
|
||||
errors = tags.literal('').join(errors)
|
||||
|
||||
label = HTML.tag('label', for_=name, c=label or prettify(name))
|
||||
inner = HTML.tag('div', class_='field', c=field)
|
||||
|
||||
outer_class = 'field-wrapper'
|
||||
if errors:
|
||||
outer_class += ' error'
|
||||
outer = HTML.tag('div', class_=outer_class, c=(errors or '') + label + inner)
|
||||
return outer
|
||||
|
||||
def referrer_field(self):
|
||||
return self.hidden('referrer', value=self.form.request.get_referrer())
|
||||
|
||||
|
||||
class UserLogin(formencode.Schema):
|
||||
allow_extra_fields = True
|
||||
filter_extra_fields = True
|
||||
|
@ -125,47 +92,6 @@ def logout(request):
|
|||
return HTTPFound(location=referrer, headers=headers)
|
||||
|
||||
|
||||
class CurrentPasswordCorrect(formencode.validators.FancyValidator):
|
||||
|
||||
def _to_python(self, value, state):
|
||||
user = state
|
||||
if not authenticate_user(user.username, value, session=Session()):
|
||||
raise formencode.Invalid("The password is incorrect.", value, state)
|
||||
return value
|
||||
|
||||
|
||||
class ChangePassword(formencode.Schema):
|
||||
|
||||
allow_extra_fields = True
|
||||
filter_extra_fields = True
|
||||
|
||||
current_password = formencode.All(
|
||||
formencode.validators.NotEmpty(),
|
||||
CurrentPasswordCorrect())
|
||||
|
||||
new_password = formencode.validators.NotEmpty()
|
||||
confirm_password = formencode.validators.NotEmpty()
|
||||
|
||||
chained_validators = [formencode.validators.FieldsMatch(
|
||||
'new_password', 'confirm_password')]
|
||||
|
||||
|
||||
def change_password(request):
|
||||
"""
|
||||
Allows a user to change his or her password.
|
||||
"""
|
||||
|
||||
if not request.user:
|
||||
return HTTPFound(location=request.route_url('home'))
|
||||
|
||||
form = Form(request, schema=ChangePassword, state=request.user)
|
||||
if form.validate():
|
||||
set_user_password(request.user, form.data['new_password'])
|
||||
return HTTPFound(location=request.get_referrer())
|
||||
|
||||
return {'form': FormRenderer(form)}
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('login', '/login')
|
||||
|
@ -173,6 +99,3 @@ def includeme(config):
|
|||
|
||||
config.add_route('logout', '/logout')
|
||||
config.add_view(logout, route_name='logout')
|
||||
|
||||
config.add_route('change_password', '/change-password')
|
||||
config.add_view(change_password, route_name='change_password', renderer='/change_password.mako')
|
||||
|
|
|
@ -59,15 +59,12 @@ class AutocompleteView(View):
|
|||
def query(self, query):
|
||||
return self.make_query(query)
|
||||
|
||||
def display(self, instance):
|
||||
return getattr(instance, self.fieldname)
|
||||
|
||||
def __call__(self):
|
||||
query = self.request.params['query']
|
||||
objs = self.query(query).all()
|
||||
data = dict(
|
||||
query=query,
|
||||
suggestions=[self.display(x) for x in objs],
|
||||
suggestions=[getattr(x, self.fieldname) for x in objs],
|
||||
data=[x.uuid for x in objs],
|
||||
)
|
||||
return data
|
||||
|
|
|
@ -26,240 +26,155 @@
|
|||
``edbob.pyramid.views.people`` -- Person Views
|
||||
"""
|
||||
|
||||
from sqlalchemy import and_
|
||||
import transaction
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
# import transaction
|
||||
# from pyramid.httpexceptions import HTTPFound
|
||||
from formalchemy import Field
|
||||
|
||||
# from formalchemy import Field
|
||||
|
||||
# from edbob.pyramid import filters
|
||||
# from edbob.pyramid import forms
|
||||
# from edbob.pyramid import grids
|
||||
# from edbob.pyramid import Session
|
||||
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView
|
||||
from edbob.db.extensions.contact.model import (
|
||||
Person, PersonEmailAddress, PersonPhoneNumber)
|
||||
import edbob
|
||||
from edbob.pyramid import filters
|
||||
from edbob.pyramid import forms
|
||||
from edbob.pyramid import grids
|
||||
from edbob.pyramid import Session
|
||||
|
||||
|
||||
class PeopleGrid(SearchableAlchemyGridView):
|
||||
def filter_map():
|
||||
return filters.get_filter_map(
|
||||
edbob.Person,
|
||||
ilike=['first_name', 'last_name', 'display_name'])
|
||||
|
||||
mapped_class = Person
|
||||
config_prefix = 'people'
|
||||
sort = 'first_name'
|
||||
def search_config(request, fmap):
|
||||
return filters.get_search_config(
|
||||
'people.list', request, fmap,
|
||||
include_filter_display_name=True,
|
||||
filter_type_display_name='lk')
|
||||
|
||||
def join_map(self):
|
||||
return {
|
||||
'email':
|
||||
lambda q: q.outerjoin(PersonEmailAddress, and_(
|
||||
PersonEmailAddress.parent_uuid == Person.uuid,
|
||||
PersonEmailAddress.preference == 1)),
|
||||
'phone':
|
||||
lambda q: q.outerjoin(PersonPhoneNumber, and_(
|
||||
PersonPhoneNumber.parent_uuid == Person.uuid,
|
||||
PersonPhoneNumber.preference == 1)),
|
||||
}
|
||||
def search_form(config):
|
||||
return filters.get_search_form(config)
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(
|
||||
ilike=['first_name', 'last_name'],
|
||||
email=self.filter_ilike(PersonEmailAddress.address),
|
||||
phone=self.filter_ilike(PersonPhoneNumber.number))
|
||||
def grid_config(request, search, fmap):
|
||||
return grids.get_grid_config(
|
||||
'people.list', request, search,
|
||||
filter_map=fmap, sort='display_name')
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_first_name=True,
|
||||
filter_type_first_name='lk',
|
||||
include_filter_last_name=True,
|
||||
filter_type_last_name='lk',
|
||||
filter_label_phone="Phone Number",
|
||||
filter_label_email="Email Address")
|
||||
def sort_map():
|
||||
return grids.get_sort_map(
|
||||
edbob.Person,
|
||||
['first_name', 'last_name', 'display_name'])
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map(
|
||||
'first_name', 'last_name',
|
||||
email=self.sorter(PersonEmailAddress.address),
|
||||
phone=self.sorter(PersonPhoneNumber.number))
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.first_name,
|
||||
g.last_name,
|
||||
g.phone.label("Phone Number"),
|
||||
g.email.label("Email Address"),
|
||||
],
|
||||
readonly=True)
|
||||
g.clickable = True
|
||||
g.click_route_name = 'person.read'
|
||||
return g
|
||||
def query(config):
|
||||
smap = sort_map()
|
||||
q = Session.query(edbob.Person)
|
||||
q = filters.filter_query(q, config)
|
||||
q = grids.sort_query(q, config, smap)
|
||||
return q
|
||||
|
||||
|
||||
class PersonCrud(CrudView):
|
||||
def people(context, request):
|
||||
|
||||
mapped_class = Person
|
||||
home_route = 'people'
|
||||
fmap = filter_map()
|
||||
config = search_config(request, fmap)
|
||||
search = search_form(config)
|
||||
config = grid_config(request, search, fmap)
|
||||
people = grids.get_pager(query, config)
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.first_name,
|
||||
fs.last_name,
|
||||
fs.phone.label("Phone Number"),
|
||||
fs.email.label("Email Address"),
|
||||
])
|
||||
return fs
|
||||
g = forms.AlchemyGrid(
|
||||
edbob.Person, people, config,
|
||||
gridurl=request.route_url('people.list'),
|
||||
objurl='person.edit')
|
||||
|
||||
g.configure(
|
||||
include=[
|
||||
g.first_name,
|
||||
g.last_name,
|
||||
g.display_name,
|
||||
],
|
||||
readonly=True)
|
||||
|
||||
grid = g.render(class_='clickable people')
|
||||
return grids.render_grid(request, grid, search)
|
||||
|
||||
|
||||
# def filter_map():
|
||||
# return filters.get_filter_map(
|
||||
# edbob.Person,
|
||||
# ilike=['first_name', 'last_name', 'display_name'])
|
||||
|
||||
# def search_config(request, fmap):
|
||||
# return filters.get_search_config(
|
||||
# 'people.list', request, fmap,
|
||||
# include_filter_display_name=True,
|
||||
# filter_type_display_name='lk')
|
||||
|
||||
# def search_form(config):
|
||||
# return filters.get_search_form(config)
|
||||
|
||||
# def grid_config(request, search, fmap):
|
||||
# return grids.get_grid_config(
|
||||
# 'people.list', request, search,
|
||||
# filter_map=fmap, sort='display_name')
|
||||
|
||||
# def sort_map():
|
||||
# return grids.get_sort_map(
|
||||
# edbob.Person,
|
||||
# ['first_name', 'last_name', 'display_name'])
|
||||
|
||||
# def query(config):
|
||||
# smap = sort_map()
|
||||
# q = Session.query(edbob.Person)
|
||||
# q = filters.filter_query(q, config)
|
||||
# q = grids.sort_query(q, config, smap)
|
||||
# return q
|
||||
def person_fieldset(person, request):
|
||||
fs = forms.make_fieldset(person, url=request.route_url,
|
||||
url_action=request.current_route_url(),
|
||||
route_name='people.list')
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.first_name,
|
||||
fs.last_name,
|
||||
fs.display_name,
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
# def people(context, request):
|
||||
def new_person(context, request):
|
||||
|
||||
# fmap = filter_map()
|
||||
# config = search_config(request, fmap)
|
||||
# search = search_form(config)
|
||||
# config = grid_config(request, search, fmap)
|
||||
# people = grids.get_pager(query, config)
|
||||
fs = person_fieldset(edbob.Person, request)
|
||||
if not fs.readonly and request.POST:
|
||||
fs.rebind(data=request.params)
|
||||
if fs.validate():
|
||||
|
||||
# g = forms.AlchemyGrid(
|
||||
# edbob.Person, people, config,
|
||||
# gridurl=request.route_url('people.list'),
|
||||
# objurl='person.edit')
|
||||
with transaction.manager:
|
||||
fs.sync()
|
||||
Session.add(fs.model)
|
||||
Session.flush()
|
||||
request.session.flash("%s \"%s\" has been %s." % (
|
||||
fs.crud_title, fs.get_display_text(),
|
||||
'updated' if fs.edit else 'created'))
|
||||
|
||||
# g.configure(
|
||||
# include=[
|
||||
# g.first_name,
|
||||
# g.last_name,
|
||||
# g.display_name,
|
||||
# ],
|
||||
# readonly=True)
|
||||
return HTTPFound(location=request.route_url('people.list'))
|
||||
|
||||
# grid = g.render(class_='clickable people')
|
||||
# return grids.render_grid(request, grid, search)
|
||||
return {'fieldset': fs, 'crud': True}
|
||||
|
||||
|
||||
# def person_fieldset(person, request):
|
||||
# fs = forms.make_fieldset(person, url=request.route_url,
|
||||
# url_action=request.current_route_url(),
|
||||
# route_name='people.list')
|
||||
# fs.configure(
|
||||
# include=[
|
||||
# fs.first_name,
|
||||
# fs.last_name,
|
||||
# fs.display_name,
|
||||
# ])
|
||||
# return fs
|
||||
def edit_person(request):
|
||||
"""
|
||||
View for editing a :class:`edbob.Person` instance.
|
||||
"""
|
||||
|
||||
from edbob.pyramid.views.users import user_fieldset
|
||||
|
||||
# def new_person(context, request):
|
||||
uuid = request.matchdict['uuid']
|
||||
person = Session.query(edbob.Person).get(uuid) if uuid else None
|
||||
assert person
|
||||
|
||||
# fs = person_fieldset(edbob.Person, request)
|
||||
# if not fs.readonly and request.POST:
|
||||
# fs.rebind(data=request.params)
|
||||
# if fs.validate():
|
||||
fs = person_fieldset(person, request)
|
||||
if request.POST:
|
||||
fs.rebind(data=request.params)
|
||||
if fs.validate():
|
||||
|
||||
# with transaction.manager:
|
||||
# fs.sync()
|
||||
# Session.add(fs.model)
|
||||
# Session.flush()
|
||||
# request.session.flash("%s \"%s\" has been %s." % (
|
||||
# fs.crud_title, fs.get_display_text(),
|
||||
# 'updated' if fs.edit else 'created'))
|
||||
with transaction.manager:
|
||||
fs.sync()
|
||||
fs.model = Session.merge(fs.model)
|
||||
request.session.flash("%s \"%s\" has been %s." % (
|
||||
fs.crud_title, fs.get_display_text(),
|
||||
'updated' if fs.edit else 'created'))
|
||||
home = request.route_url('people.list')
|
||||
|
||||
# return HTTPFound(location=request.route_url('people.list'))
|
||||
return HTTPFound(location=home)
|
||||
|
||||
# return {'fieldset': fs, 'crud': True}
|
||||
user = fs.model.user
|
||||
if user:
|
||||
user = user_fieldset(user, request)
|
||||
user.readonly = True
|
||||
del user.person
|
||||
del user.password
|
||||
del user.confirm_password
|
||||
|
||||
|
||||
# def edit_person(request):
|
||||
# """
|
||||
# View for editing a :class:`edbob.Person` instance.
|
||||
# """
|
||||
|
||||
# from edbob.pyramid.views.users import user_fieldset
|
||||
|
||||
# uuid = request.matchdict['uuid']
|
||||
# person = Session.query(edbob.Person).get(uuid) if uuid else None
|
||||
# assert person
|
||||
|
||||
# fs = person_fieldset(person, request)
|
||||
# if request.POST:
|
||||
# fs.rebind(data=request.params)
|
||||
# if fs.validate():
|
||||
|
||||
# with transaction.manager:
|
||||
# fs.sync()
|
||||
# fs.model = Session.merge(fs.model)
|
||||
# request.session.flash("%s \"%s\" has been %s." % (
|
||||
# fs.crud_title, fs.get_display_text(),
|
||||
# 'updated' if fs.edit else 'created'))
|
||||
# home = request.route_url('people.list')
|
||||
|
||||
# return HTTPFound(location=home)
|
||||
|
||||
# user = fs.model.user
|
||||
# if user:
|
||||
# user = user_fieldset(user, request)
|
||||
# user.readonly = True
|
||||
# del user.person
|
||||
# del user.password
|
||||
# del user.confirm_password
|
||||
|
||||
# return {'fieldset': fs, 'crud': True, 'user': user}
|
||||
return {'fieldset': fs, 'crud': True, 'user': user}
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('people', '/people')
|
||||
config.add_view(PeopleGrid, route_name='people',
|
||||
renderer='/people/index.mako',
|
||||
permission='people.list')
|
||||
config.add_route('people.list', '/people')
|
||||
config.add_view(people, route_name='people.list', renderer='/people/index.mako',
|
||||
permission='people.list', http_cache=0)
|
||||
|
||||
# config.add_route('people.list', '/people')
|
||||
# config.add_view(people, route_name='people.list', renderer='/people/index.mako',
|
||||
# permission='people.list', http_cache=0)
|
||||
config.add_route('person.new', '/people/new')
|
||||
config.add_view(new_person, route_name='person.new', renderer='/people/person.mako',
|
||||
permission='people.create', http_cache=0)
|
||||
|
||||
config.add_route('person.read', '/people/{uuid}')
|
||||
config.add_view(PersonCrud, attr='read', route_name='person.read',
|
||||
renderer='/people/crud.mako',
|
||||
permission='people.read')
|
||||
|
||||
# config.add_route('person.new', '/people/new')
|
||||
# config.add_view(new_person, route_name='person.new', renderer='/people/person.mako',
|
||||
# permission='people.create', http_cache=0)
|
||||
|
||||
# config.add_route('person.edit', '/people/{uuid}/edit')
|
||||
# config.add_view(edit_person, route_name='person.edit', renderer='/people/person.mako',
|
||||
# permission='people.edit', http_cache=0)
|
||||
config.add_route('person.edit', '/people/{uuid}/edit')
|
||||
config.add_view(edit_person, route_name='person.edit', renderer='/people/person.mako',
|
||||
permission='people.edit', http_cache=0)
|
||||
|
|
|
@ -32,10 +32,8 @@ from edbob.pyramid.progress import get_progress_session
|
|||
def progress(request):
|
||||
key = request.matchdict['key']
|
||||
session = get_progress_session(request.session, key)
|
||||
if session.get('complete'):
|
||||
request.session.flash(session.get('success_msg', "The process has completed successfully."))
|
||||
elif session.get('error'):
|
||||
request.session.flash(session.get('error_msg', "An unspecified error occurred."), 'error')
|
||||
if session.get('complete') and session.get('success_msg'):
|
||||
request.session.flash(session['success_msg'])
|
||||
return session
|
||||
|
||||
|
||||
|
@ -43,9 +41,9 @@ def cancel(request):
|
|||
key = request.matchdict['key']
|
||||
session = get_progress_session(request.session, key)
|
||||
session.clear()
|
||||
session['canceled'] = True
|
||||
session['cancelled'] = True
|
||||
session.save()
|
||||
msg = request.params.get('cancel_msg', "The operation was canceled.")
|
||||
msg = request.params.get('cancel_msg', "The operation was cancelled.")
|
||||
request.session.flash(msg)
|
||||
return {}
|
||||
|
||||
|
|
|
@ -26,83 +26,75 @@
|
|||
``edbob.pyramid.views.roles`` -- Role Views
|
||||
"""
|
||||
|
||||
import transaction
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
import formalchemy
|
||||
from webhelpers.html import tags
|
||||
from webhelpers.html.builder import HTML
|
||||
from formalchemy import Field, FieldRenderer
|
||||
from webhelpers.html import literal
|
||||
from webhelpers.html.tags import checkbox, hidden
|
||||
|
||||
from edbob.db import auth
|
||||
import edbob
|
||||
from edbob.db.auth import administrator_role, has_permission
|
||||
from edbob.pyramid import filters
|
||||
from edbob.pyramid import forms
|
||||
from edbob.pyramid import grids
|
||||
from edbob.pyramid import Session
|
||||
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView
|
||||
from edbob.db.extensions.auth.model import Role
|
||||
|
||||
|
||||
default_permissions = [
|
||||
def filter_map():
|
||||
return filters.get_filter_map(
|
||||
edbob.Role,
|
||||
ilike=['name'])
|
||||
|
||||
("People", [
|
||||
('people.list', "List People"),
|
||||
('people.read', "View Person"),
|
||||
('people.create', "Create Person"),
|
||||
('people.update', "Edit Person"),
|
||||
('people.delete', "Delete Person"),
|
||||
]),
|
||||
def search_config(request, fmap):
|
||||
return filters.get_search_config(
|
||||
'roles.list', request, fmap,
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
|
||||
("Roles", [
|
||||
('roles.list', "List Roles"),
|
||||
('roles.read', "View Role"),
|
||||
('roles.create', "Create Role"),
|
||||
('roles.update', "Edit Role"),
|
||||
('roles.delete', "Delete Role"),
|
||||
]),
|
||||
def search_form(config):
|
||||
return filters.get_search_form(config)
|
||||
|
||||
("Users", [
|
||||
('users.list', "List Users"),
|
||||
('users.read', "View User"),
|
||||
('users.create', "Create User"),
|
||||
('users.update', "Edit User"),
|
||||
('users.delete', "Delete User"),
|
||||
]),
|
||||
]
|
||||
def grid_config(request, search, fmap):
|
||||
return grids.get_grid_config(
|
||||
'roles.list', request, search,
|
||||
filter_map=fmap, sort='name')
|
||||
|
||||
def sort_map():
|
||||
return grids.get_sort_map(edbob.Role, ['name'])
|
||||
|
||||
def query(config):
|
||||
smap = sort_map()
|
||||
q = Session.query(edbob.Role)
|
||||
q = filters.filter_query(q, config)
|
||||
q = grids.sort_query(q, config, smap)
|
||||
return q
|
||||
|
||||
|
||||
class RolesGrid(SearchableAlchemyGridView):
|
||||
def roles(request):
|
||||
|
||||
mapped_class = Role
|
||||
config_prefix = 'roles'
|
||||
sort = 'name'
|
||||
fmap = filter_map()
|
||||
config = search_config(request, fmap)
|
||||
search = search_form(config)
|
||||
config = grid_config(request, search, fmap)
|
||||
roles = grids.get_pager(query, config)
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(ilike=['name'])
|
||||
g = forms.AlchemyGrid(
|
||||
edbob.Role, roles, config,
|
||||
gridurl=request.route_url('roles.list'),
|
||||
objurl='role.edit')
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
g.configure(
|
||||
include=[
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('roles.read'):
|
||||
g.clickable = True
|
||||
g.click_route_name = 'role.read'
|
||||
if self.request.has_perm('roles.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'role.update'
|
||||
if self.request.has_perm('roles.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'role.delete'
|
||||
return g
|
||||
grid = g.render(class_='clickable roles')
|
||||
return grids.render_grid(request, grid, search)
|
||||
|
||||
|
||||
class PermissionsField(formalchemy.Field):
|
||||
class PermissionsField(Field):
|
||||
|
||||
def sync(self):
|
||||
if not self.is_readonly():
|
||||
|
@ -110,108 +102,154 @@ class PermissionsField(formalchemy.Field):
|
|||
role.permissions = self.renderer.deserialize()
|
||||
|
||||
|
||||
def PermissionsFieldRenderer(permissions, *args, **kwargs):
|
||||
class PermissionsFieldRenderer(FieldRenderer):
|
||||
|
||||
perms = permissions
|
||||
available_permissions = [
|
||||
|
||||
("Batches", [
|
||||
('batches.list', "List Batches"),
|
||||
('batches.edit', "Edit Batch"),
|
||||
('batches.create', "Create Batch"),
|
||||
]),
|
||||
|
||||
("Roles", [
|
||||
('roles.list', "List Roles"),
|
||||
('roles.edit', "Edit Role"),
|
||||
('roles.create', "Create Role"),
|
||||
]),
|
||||
]
|
||||
|
||||
def deserialize(self):
|
||||
perms = []
|
||||
i = len(self.name) + 1
|
||||
for key in self.params:
|
||||
if key.startswith(self.name):
|
||||
perms.append(key[i:])
|
||||
return perms
|
||||
|
||||
def _render(self, readonly=False, **kwargs):
|
||||
# result = literal('')
|
||||
# for group_name, group_label, perm_list in self.field.model_value:
|
||||
# rendered_group_name = literal('<p class="permission-group">' + group_label + '</p>\n')
|
||||
# if readonly:
|
||||
# result += literal('<tr><td colspan="2">') + rendered_group_name + literal('</td></tr>')
|
||||
# else:
|
||||
# result += rendered_group_name
|
||||
# result += literal('<div>')
|
||||
# for perm_name, perm_label, checked in perm_list:
|
||||
# if readonly:
|
||||
# result += literal('<tr>'
|
||||
# + '<td class="permission">' + ('[X]' if checked else '[ ]') + '</td>'
|
||||
# + '<td class="permission-label">' + perm_label + '</td>'
|
||||
# + '</tr>\n')
|
||||
# else:
|
||||
# name = '.'.join((self.name, group_name, perm_name))
|
||||
# result += check_box(name, label=perm_label, checked=checked)
|
||||
# if not readonly:
|
||||
# result += literal('</div>')
|
||||
# if readonly:
|
||||
# return literal('<table class="permissions">') + result + literal('</table>')
|
||||
# return literal('<div class="permissions">') + result + literal('</div>')
|
||||
|
||||
role = self.field.model
|
||||
if role is administrator_role(Session()):
|
||||
res = literal('<p>This is the administrative role; '
|
||||
'it has full access to the entire system.</p>')
|
||||
if not readonly:
|
||||
res += hidden(self.name, value='') # ugly hack..or good idea?
|
||||
else:
|
||||
res = ''
|
||||
for group, perms in self.available_permissions:
|
||||
res += literal('<p class="group">%s</p>' % group)
|
||||
for perm, title in perms:
|
||||
if readonly:
|
||||
res += literal('<p>%s</p>' % title)
|
||||
else:
|
||||
checked = has_permission(role, perm)
|
||||
res += checkbox(self.name + '-' + perm,
|
||||
checked=checked, label=title)
|
||||
return res
|
||||
|
||||
def render(self, **kwargs):
|
||||
return self._render(**kwargs)
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
return self._render(readonly=True, **kwargs)
|
||||
|
||||
|
||||
def role_fieldset(role, request):
|
||||
fs = forms.make_fieldset(role, url=request.route_url,
|
||||
url_action=request.current_route_url(),
|
||||
route_name='roles.list')
|
||||
|
||||
class PermissionsFieldRenderer(formalchemy.FieldRenderer):
|
||||
fs.append(PermissionsField('permissions',
|
||||
renderer=PermissionsFieldRenderer))
|
||||
|
||||
permissions = perms
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.name,
|
||||
fs.permissions,
|
||||
])
|
||||
|
||||
def deserialize(self):
|
||||
perms = []
|
||||
i = len(self.name) + 1
|
||||
for key in self.params:
|
||||
if key.startswith(self.name):
|
||||
perms.append(key[i:])
|
||||
return perms
|
||||
if not fs.edit:
|
||||
del fs.permissions
|
||||
|
||||
def _render(self, readonly=False, **kwargs):
|
||||
role = self.field.model
|
||||
admin = auth.administrator_role(Session())
|
||||
if role is admin:
|
||||
html = HTML.tag('p', c="This is the administrative role; "
|
||||
"it has full access to the entire system.")
|
||||
if not readonly:
|
||||
html += tags.hidden(self.name, value='') # ugly hack..or good idea?
|
||||
else:
|
||||
html = ''
|
||||
for group, perms in self.permissions:
|
||||
inner = HTML.tag('p', c=group)
|
||||
for perm, title in perms:
|
||||
checked = auth.has_permission(
|
||||
role, perm, include_guest=False, session=Session())
|
||||
if readonly:
|
||||
span = HTML.tag('span', c="[X]" if checked else "[ ]")
|
||||
inner += HTML.tag('p', class_='perm', c=span + ' ' + title)
|
||||
else:
|
||||
inner += tags.checkbox(self.name + '-' + perm,
|
||||
checked=checked, label=title)
|
||||
html += HTML.tag('div', class_='group', c=inner)
|
||||
return html
|
||||
|
||||
def render(self, **kwargs):
|
||||
return self._render(**kwargs)
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
return self._render(readonly=True, **kwargs)
|
||||
|
||||
return PermissionsFieldRenderer
|
||||
return fs
|
||||
|
||||
|
||||
class RoleCrud(CrudView):
|
||||
def new_role(request):
|
||||
|
||||
mapped_class = Role
|
||||
home_route = 'roles'
|
||||
permissions = default_permissions
|
||||
fs = role_fieldset(edbob.Role, request)
|
||||
if request.POST:
|
||||
fs.rebind(data=request.params)
|
||||
if fs.validate():
|
||||
|
||||
def fieldset(self, role):
|
||||
fs = self.make_fieldset(role)
|
||||
fs.append(PermissionsField(
|
||||
'permissions',
|
||||
renderer=PermissionsFieldRenderer(self.permissions)))
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.name,
|
||||
fs.permissions,
|
||||
])
|
||||
return fs
|
||||
with transaction.manager:
|
||||
fs.sync()
|
||||
fs.model = Session.merge(fs.model)
|
||||
request.session.flash("%s \"%s\" has been %s." % (
|
||||
fs.crud_title, fs.get_display_text(),
|
||||
'updated' if fs.edit else 'created'))
|
||||
home = request.route_url('roles.list')
|
||||
|
||||
def pre_delete(self, model):
|
||||
admin = auth.administrator_role(Session())
|
||||
guest = auth.guest_role(Session())
|
||||
if model in (admin, guest):
|
||||
self.request.session.flash("You may not delete the %s role." % str(model), 'error')
|
||||
return HTTPFound(location=self.request.get_referrer())
|
||||
return HTTPFound(location=home)
|
||||
|
||||
return {'fieldset': fs, 'crud': True}
|
||||
|
||||
|
||||
def edit_role(request):
|
||||
uuid = request.matchdict['uuid']
|
||||
role = Session.query(edbob.Role).get(uuid) if uuid else None
|
||||
assert role
|
||||
|
||||
fs = role_fieldset(role, request)
|
||||
if request.POST:
|
||||
fs.rebind(data=request.params)
|
||||
if fs.validate():
|
||||
|
||||
with transaction.manager:
|
||||
Session.add(fs.model)
|
||||
fs.sync()
|
||||
request.session.flash("%s \"%s\" has been %s." % (
|
||||
fs.crud_title, fs.get_display_text(),
|
||||
'updated' if fs.edit else 'created'))
|
||||
home = request.route_url('roles.list')
|
||||
|
||||
return HTTPFound(location=home)
|
||||
|
||||
return {'fieldset': fs, 'crud': True}
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('roles', '/roles')
|
||||
config.add_view(RolesGrid, route_name='roles',
|
||||
renderer='/roles/index.mako',
|
||||
permission='roles.list')
|
||||
|
||||
settings = config.get_settings()
|
||||
perms = settings.get('edbob.permissions')
|
||||
if perms:
|
||||
RoleCrud.permissions = perms
|
||||
config.add_route('roles.list', '/roles')
|
||||
config.add_view(roles, route_name='roles.list', renderer='/roles/index.mako',
|
||||
permission='roles.list', http_cache=0)
|
||||
|
||||
config.add_route('role.create', '/roles/new')
|
||||
config.add_view(RoleCrud, attr='create', route_name='role.create',
|
||||
renderer='/roles/crud.mako',
|
||||
permission='roles.create')
|
||||
config.add_route('role.new', '/roles/new')
|
||||
config.add_view(new_role, route_name='role.new', renderer='/roles/role.mako',
|
||||
permission='roles.create', http_cache=0)
|
||||
|
||||
config.add_route('role.read', '/roles/{uuid}')
|
||||
config.add_view(RoleCrud, attr='read', route_name='role.read',
|
||||
renderer='/roles/crud.mako',
|
||||
permission='roles.read')
|
||||
|
||||
config.add_route('role.update', '/roles/{uuid}/edit')
|
||||
config.add_view(RoleCrud, attr='update', route_name='role.update',
|
||||
renderer='/roles/crud.mako',
|
||||
permission='roles.update')
|
||||
|
||||
config.add_route('role.delete', '/roles/{uuid}/delete')
|
||||
config.add_view(RoleCrud, attr='delete', route_name='role.delete',
|
||||
permission='roles.delete')
|
||||
config.add_route('role.edit', '/roles/{uuid}/edit')
|
||||
config.add_view(edit_role, route_name='role.edit', renderer='/roles/role.mako',
|
||||
permission='roles.edit', http_cache=0)
|
||||
|
|
|
@ -26,35 +26,35 @@
|
|||
``edbob.pyramid.views.users`` -- User Views
|
||||
"""
|
||||
|
||||
from webhelpers.html import tags
|
||||
from webhelpers.html.builder import HTML
|
||||
from webhelpers.html import literal, tags
|
||||
|
||||
import formalchemy
|
||||
from formalchemy.fields import SelectFieldRenderer
|
||||
|
||||
from edbob.db import auth
|
||||
import edbob
|
||||
from edbob.db.auth import set_user_password
|
||||
from edbob.pyramid import Session
|
||||
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView
|
||||
from edbob.db.extensions.auth.model import User, Role
|
||||
from edbob.db.extensions.contact.model import Person
|
||||
from edbob.pyramid.views import SearchableAlchemyGridView
|
||||
from edbob.pyramid.views.crud import Crud
|
||||
|
||||
|
||||
class UsersGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = User
|
||||
config_prefix = 'users'
|
||||
mapped_class = edbob.User
|
||||
route_name = 'users'
|
||||
route_url = '/users'
|
||||
sort = 'username'
|
||||
|
||||
def join_map(self):
|
||||
return {
|
||||
'person':
|
||||
lambda q: q.outerjoin(Person),
|
||||
lambda q: q.outerjoin(edbob.Person),
|
||||
}
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(
|
||||
ilike=['username'],
|
||||
person=self.filter_ilike(Person.display_name))
|
||||
person=self.filter_ilike(edbob.Person.display_name))
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
|
@ -66,7 +66,7 @@ class UsersGrid(SearchableAlchemyGridView):
|
|||
def sort_map(self):
|
||||
return self.make_sort_map(
|
||||
'username',
|
||||
person=self.sorter(Person.display_name))
|
||||
person=self.sorter(edbob.Person.display_name))
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
|
@ -76,34 +76,25 @@ class UsersGrid(SearchableAlchemyGridView):
|
|||
g.person,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('users.read'):
|
||||
g.clickable = True
|
||||
g.click_route_name = 'user.read'
|
||||
if self.request.has_perm('users.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'user.update'
|
||||
if self.request.has_perm('users.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'user.delete'
|
||||
return g
|
||||
|
||||
|
||||
class _RolesFieldRenderer(SelectFieldRenderer):
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
roles = Session.query(edbob.Role)
|
||||
res = literal('<ul>')
|
||||
for uuid in self.value:
|
||||
role = roles.get(uuid)
|
||||
res += literal('<li>%s</li>' % (
|
||||
tags.link_to(role.name,
|
||||
self.request.route_url('role.edit', uuid=role.uuid))))
|
||||
res += literal('</ul>')
|
||||
return res
|
||||
|
||||
|
||||
def RolesFieldRenderer(request):
|
||||
|
||||
class RolesFieldRenderer(SelectFieldRenderer):
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
roles = Session.query(Role)
|
||||
html = ''
|
||||
for uuid in self.value:
|
||||
role = roles.get(uuid)
|
||||
link = tags.link_to(
|
||||
role.name, request.route_url('role.read', uuid=role.uuid))
|
||||
html += HTML.tag('li', c=link)
|
||||
html = HTML.tag('ul', c=html)
|
||||
return html
|
||||
|
||||
return RolesFieldRenderer
|
||||
return type('RolesFieldRenderer', (_RolesFieldRenderer,), {'request': request})
|
||||
|
||||
|
||||
class RolesField(formalchemy.Field):
|
||||
|
@ -118,15 +109,14 @@ class RolesField(formalchemy.Field):
|
|||
return [x.uuid for x in user.roles]
|
||||
|
||||
def get_options(self):
|
||||
q = Session.query(Role.name, Role.uuid)
|
||||
q = q.filter(Role.uuid != auth.guest_role(Session()).uuid)
|
||||
q = q.order_by(Role.name)
|
||||
q = Session.query(edbob.Role.name, edbob.Role.uuid)
|
||||
q = q.order_by(edbob.Role.name)
|
||||
return q.all()
|
||||
|
||||
def sync(self):
|
||||
if not self.is_readonly():
|
||||
user = self.model
|
||||
roles = Session.query(Role)
|
||||
roles = Session.query(edbob.Role)
|
||||
data = self.renderer.deserialize()
|
||||
user.roles = [roles.get(x) for x in data]
|
||||
|
||||
|
@ -141,7 +131,7 @@ class _ProtectedPersonRenderer(formalchemy.FieldRenderer):
|
|||
|
||||
|
||||
def ProtectedPersonRenderer(uuid):
|
||||
person = Session.query(Person).get(uuid)
|
||||
person = Session.query(edbob.Person).get(uuid)
|
||||
assert person
|
||||
return type('ProtectedPersonRenderer', (_ProtectedPersonRenderer,),
|
||||
{'person': person})
|
||||
|
@ -183,13 +173,13 @@ class PasswordField(formalchemy.Field):
|
|||
if not self.is_readonly():
|
||||
password = self.renderer.deserialize()
|
||||
if password:
|
||||
auth.set_user_password(self.model, password)
|
||||
set_user_password(self.model, password)
|
||||
|
||||
|
||||
class UserCrud(CrudView):
|
||||
class UserCrud(Crud):
|
||||
|
||||
mapped_class = User
|
||||
home_route = 'users'
|
||||
mapped_class = edbob.User
|
||||
home_route = 'users.list'
|
||||
|
||||
def fieldset(self, user):
|
||||
fs = self.make_fieldset(user)
|
||||
|
@ -209,12 +199,8 @@ class UserCrud(CrudView):
|
|||
fs.roles,
|
||||
])
|
||||
|
||||
if self.readonly:
|
||||
del fs.password
|
||||
del fs.confirm_password
|
||||
|
||||
# if fs.edit and user.person:
|
||||
if isinstance(user, User) and user.person:
|
||||
if isinstance(user, edbob.User) and user.person:
|
||||
fs.person.set(readonly=True,
|
||||
renderer=LinkedPersonRenderer(self.request))
|
||||
|
||||
|
@ -227,27 +213,5 @@ class UserCrud(CrudView):
|
|||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('users', '/users')
|
||||
config.add_view(UsersGrid, route_name='users',
|
||||
renderer='/users/index.mako',
|
||||
permission='users.list')
|
||||
|
||||
config.add_route('user.create', '/users/new')
|
||||
config.add_view(UserCrud, attr='create', route_name='user.create',
|
||||
renderer='/users/crud.mako',
|
||||
permission='users.create')
|
||||
|
||||
config.add_route('user.read', '/users/{uuid}')
|
||||
config.add_view(UserCrud, attr='read', route_name='user.read',
|
||||
renderer='/users/crud.mako',
|
||||
permission='users.read')
|
||||
|
||||
config.add_route('user.update', '/users/{uuid}/edit')
|
||||
config.add_view(UserCrud, attr='update', route_name='user.update',
|
||||
renderer='/users/crud.mako',
|
||||
permission='users.update')
|
||||
|
||||
config.add_route('user.delete', '/users/{uuid}/delete')
|
||||
config.add_view(UserCrud, attr='delete', route_name='user.delete',
|
||||
permission='users.delete')
|
||||
UsersGrid.add_route(config)
|
||||
UserCrud.add_routes(config)
|
||||
|
|
|
@ -42,7 +42,7 @@ def run_migrations_offline():
|
|||
|
||||
"""
|
||||
|
||||
url = edbob.config.require('edbob.db', 'default.url')
|
||||
url = edbob.config.require('edbob.db', 'sqlalchemy.url')
|
||||
context.configure(url=url)
|
||||
|
||||
with context.begin_transaction():
|
||||
|
|
|
@ -24,20 +24,8 @@ active_extensions = sa.Table(
|
|||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else 'pass'}
|
||||
|
||||
# active extensions
|
||||
|
||||
# op.execute(
|
||||
# active_extensions.insert().values(
|
||||
# name='dummy'))
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else 'pass'}
|
||||
|
||||
# active extensions
|
||||
|
||||
# op.execute(
|
||||
# active_extensions.delete().where(
|
||||
# active_extensions.c.name == 'dummy'))
|
||||
${downgrades if downgrades else "pass"}
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
import os.path
|
||||
|
||||
from pyramid.config import Configurator
|
||||
from pyramid.authentication import SessionAuthenticationPolicy
|
||||
|
||||
import edbob
|
||||
from edbob.pyramid.auth import EdbobAuthorizationPolicy
|
||||
|
||||
|
||||
def main(global_config, **settings):
|
||||
|
@ -25,18 +27,24 @@ def main(global_config, **settings):
|
|||
# * Raise an exception if a setting is missing or invalid.
|
||||
# * Convert values from strings to their intended type.
|
||||
|
||||
settings.setdefault('mako.directories', [
|
||||
'{{package}}.pyramid:templates',
|
||||
'edbob.pyramid:templates',
|
||||
])
|
||||
|
||||
# Make two attempts when "retryable" errors happen during transactions.
|
||||
settings.setdefault('tm.attempts', 2)
|
||||
settings['mako.directories'] = [
|
||||
'{{package}}.pyramid:templates',
|
||||
'edbob.pyramid:templates',
|
||||
]
|
||||
|
||||
config = Configurator(settings=settings)
|
||||
|
||||
# Configure edbob
|
||||
edbob.init('{{package}}', os.path.abspath(settings['edbob.config']))
|
||||
|
||||
# Configure session
|
||||
config.include('pyramid_beaker')
|
||||
|
||||
# Configure auth
|
||||
config.set_authentication_policy(SessionAuthenticationPolicy())
|
||||
config.set_authorization_policy(EdbobAuthorizationPolicy())
|
||||
|
||||
# Include "core" stuff provided by edbob.
|
||||
config.include('edbob.pyramid')
|
||||
|
||||
# Additional config is defined elsewhere within {{project}}. This includes
|
||||
|
@ -45,8 +53,4 @@ def main(global_config, **settings):
|
|||
config.include('{{package}}.pyramid.subscribers')
|
||||
config.include('{{package}}.pyramid.views')
|
||||
|
||||
# Consider PostgreSQL server restart errors to be "retryable."
|
||||
config.add_tween('edbob.pyramid.tweens.sqlerror_tween_factory',
|
||||
under='pyramid_tm.tm_tween_factory')
|
||||
|
||||
return config.make_wsgi_app()
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
include *.txt *.ini *.cfg *.rst
|
||||
recursive-include {{package}} *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
|
||||
recursive-include {{package}}/alembic *.py
|
||||
|
|
|
@ -69,6 +69,7 @@ setup(
|
|||
],
|
||||
|
||||
install_requires = requires,
|
||||
tests_require = requires,
|
||||
|
||||
packages = find_packages(),
|
||||
include_package_data = True,
|
||||
|
|
|
@ -29,64 +29,23 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from sqlalchemy import Table, Column, String
|
||||
from sqlalchemy import engine_from_config as _engine_from_config
|
||||
|
||||
from edbob.core import get_uuid
|
||||
from edbob.modules import load_spec
|
||||
from edbob.time import utc_time
|
||||
|
||||
|
||||
__all__ = ['getset_factory', 'table_with_uuid', 'current_time']
|
||||
|
||||
|
||||
def current_time(context):
|
||||
"""
|
||||
This function may be provided to the ``default`` parameter of a
|
||||
:class:`sqlalchemy.Column` class definition. Doing so will ensure the
|
||||
column's default value will be the current time in UTC.
|
||||
"""
|
||||
|
||||
return utc_time(naive=True)
|
||||
|
||||
|
||||
def engine_from_config(config, prefix='sqlalchemy.', **kwargs):
|
||||
"""
|
||||
Slightly enhanced version of the :func:`sqlalchemy.engine_from_config()`
|
||||
function. This version is aware of the ``poolclass`` configuration
|
||||
parameter, and will coerce it via :func:`edbob.load_spec()`.
|
||||
|
||||
Note that if a pool class is specified, the class should be represented
|
||||
using the "spec" format and *not* pure dotted path notation, e.g.:
|
||||
|
||||
Correct::
|
||||
|
||||
[edbob.db]
|
||||
default.poolclass = sqlqlchemy.pool:NullPool
|
||||
|
||||
Incorrect::
|
||||
|
||||
[edbob.db]
|
||||
default.poolclass = sqlalchemy.pool.NullPool
|
||||
"""
|
||||
|
||||
if config.has_key(prefix + 'poolclass'):
|
||||
config[prefix + 'poolclass'] = load_spec(config[prefix + 'poolclass'])
|
||||
return _engine_from_config(config, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
def getset_factory(collection_class, proxy):
|
||||
"""
|
||||
Get/set factory for SQLAlchemy association proxy attributes.
|
||||
Helper function, useful for SQLAlchemy's "association proxy" configuration.
|
||||
"""
|
||||
|
||||
def getter(obj):
|
||||
if obj is None:
|
||||
return None
|
||||
if obj is None: return None
|
||||
return getattr(obj, proxy.value_attr)
|
||||
|
||||
def setter(obj, val):
|
||||
setattr(obj, proxy.value_attr, val)
|
||||
|
||||
setter = lambda obj, val: setattr(obj, proxy.value_attr, val)
|
||||
return getter, setter
|
||||
|
||||
|
||||
|
@ -114,3 +73,13 @@ def table_with_uuid(name, metadata, *args, **kwargs):
|
|||
return Table(name, metadata,
|
||||
Column('uuid', String(32), primary_key=True, default=get_uuid),
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
def current_time(context):
|
||||
"""
|
||||
This function may be provided to the ``default`` parameter of a
|
||||
:class:`sqlalchemy.Column` class definition. Doing so will ensure the
|
||||
column's default value will be the current time in UTC.
|
||||
"""
|
||||
|
||||
return utc_time(naive=True)
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
An unhandled exception occurred.
|
||||
|
||||
*Machine Name:* ${host_name} (${host_ip})
|
||||
|
||||
*Machine Time:* ${host_time.strftime('%Y-%m-%d %H:%M:%S %Z%z')}
|
||||
|
||||
<pre>
|
||||
${traceback}
|
||||
</pre>
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
import unittest
|
||||
|
||||
from pyramid import testing
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
"""
|
||||
Base class for all test suites.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.config = testing.setUp()
|
||||
|
||||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
|
||||
def test_something(self):
|
||||
self.assertTrue(1)
|
|
@ -75,7 +75,7 @@ def init(config):
|
|||
tz = config.get('edbob.time', key)
|
||||
if tz:
|
||||
key = key[5:]
|
||||
log.debug("'%s' timezone set to '%s'" % (key, tz))
|
||||
log.info("'%s' timezone set to '%s'" % (key, tz))
|
||||
set_timezone(tz, key)
|
||||
|
||||
if 'local' not in timezones:
|
||||
|
|
|
@ -31,13 +31,6 @@ from pkg_resources import iter_entry_points
|
|||
import edbob
|
||||
|
||||
|
||||
# Import OrderedDict for the sake of other modules.
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
|
||||
def entry_point_map(key):
|
||||
"""
|
||||
Convenience function to retrieve a dictionary of entry points, keyed by
|
||||
|
|
102
edbob/win32.py
102
edbob/win32.py
|
@ -28,104 +28,16 @@
|
|||
|
||||
import sys
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
if sys.platform == 'win32': # docs should build for everyone
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32con
|
||||
import win32event
|
||||
import win32file
|
||||
import win32print
|
||||
import win32service
|
||||
import winerror
|
||||
|
||||
try:
|
||||
import win32serviceutil
|
||||
except ImportError:
|
||||
# Mock out for testing on Linux.
|
||||
class Object(object):
|
||||
pass
|
||||
win32serviceutil = Object()
|
||||
win32serviceutil.ServiceFramework = Object
|
||||
|
||||
import edbob
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Service(win32serviceutil.ServiceFramework):
|
||||
"""
|
||||
Base class for Windows service implementations.
|
||||
"""
|
||||
|
||||
appname = 'edbob'
|
||||
|
||||
def __init__(self, args):
|
||||
"""
|
||||
Constructor.
|
||||
"""
|
||||
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
|
||||
# Create "wait stop" event, for main worker loop.
|
||||
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
||||
|
||||
def Initialize(self):
|
||||
"""
|
||||
Service initialization.
|
||||
"""
|
||||
|
||||
# Read configuration file(s).
|
||||
edbob.init(self.appname, service=self._svc_name_)
|
||||
|
||||
return True
|
||||
|
||||
def SvcDoRun(self):
|
||||
"""
|
||||
This method is invoked when the service starts.
|
||||
"""
|
||||
|
||||
import servicemanager
|
||||
|
||||
# Write start occurrence to Windows Event Log.
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ''))
|
||||
|
||||
# Figure out what we're supposed to be doing.
|
||||
if self.Initialize():
|
||||
|
||||
# Wait infinitely for stop request, while threads do their thing.
|
||||
log.info("SvcDoRun: All threads started; waiting for stop request.")
|
||||
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
|
||||
log.info("SvcDoRun: Stop request received.")
|
||||
|
||||
else: # Nothing to be done...
|
||||
msg = "Nothing to do! (Initialization failed.)"
|
||||
servicemanager.LogWarningMsg(msg)
|
||||
log.warning("SvcDoRun: %s" % msg)
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
|
||||
# Write stop occurrence to Windows Event Log.
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STOPPED,
|
||||
(self._svc_name_, ''))
|
||||
|
||||
def SvcStop(self):
|
||||
"""
|
||||
This method is invoked when the service is requested to stop itself.
|
||||
"""
|
||||
|
||||
# Let the SCM know we're trying to stop.
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
|
||||
# Let worker loop know its job is done.
|
||||
win32event.SetEvent(self.hWaitStop)
|
||||
|
||||
|
||||
def RegDeleteTree(key, subkey):
|
||||
"""
|
||||
|
@ -171,20 +83,6 @@ def RegDeleteTree(key, subkey):
|
|||
pass
|
||||
|
||||
|
||||
def capture_output(command):
|
||||
"""
|
||||
Runs ``command`` and returns any output it produces.
|
||||
"""
|
||||
|
||||
# We *need* to pipe ``stdout`` because that's how we capture the output of
|
||||
# the ``hg`` command. However, we must pipe *all* handles in order to
|
||||
# prevent issues when running as a GUI but *from* the Windows console.
|
||||
# See also: http://bugs.python.org/issue3905
|
||||
kwargs = dict(stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
output = subprocess.Popen(command, **kwargs).communicate()[0]
|
||||
return output
|
||||
|
||||
|
||||
def delayed_auto_start_service(name):
|
||||
"""
|
||||
Configures the Windows service named ``name`` such that its startup type is
|
||||
|
|
36
fabfile.py
vendored
36
fabfile.py
vendored
|
@ -1,36 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# edbob -- Pythonic Software Framework
|
||||
# Copyright © 2010-2012 Lance Edgar
|
||||
#
|
||||
# This file is part of edbob.
|
||||
#
|
||||
# edbob is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
import shutil
|
||||
|
||||
from fabric.api import *
|
||||
|
||||
|
||||
@task
|
||||
def release():
|
||||
"""
|
||||
Release a new version of 'edbob'.
|
||||
"""
|
||||
shutil.rmtree('edbob.egg-info')
|
||||
local('python setup.py sdist --formats=gztar register upload')
|
|
@ -1,7 +1,2 @@
|
|||
[nosetests]
|
||||
nocapture = 1
|
||||
cover-package = edbob
|
||||
cover-erase = 1
|
||||
cover-inclusive = 1
|
||||
cover-html = 1
|
||||
cover-html-dir = htmlcov
|
||||
[egg_info]
|
||||
tag_build = .dev
|
||||
|
|
8
setup.py
8
setup.py
|
@ -73,10 +73,7 @@ requires = [
|
|||
'decorator', # 3.3.2
|
||||
'lockfile', # 0.9.1
|
||||
'progressbar', # 2.3
|
||||
|
||||
# Hardcode ``pytz`` minimum since apparently it isn't (any longer?) enough
|
||||
# to simply require the library.
|
||||
'pytz>=2013b', # 2013b
|
||||
'pytz', # 2012b
|
||||
]
|
||||
|
||||
if sys.version_info < (2, 7):
|
||||
|
@ -97,7 +94,6 @@ extras = {
|
|||
#
|
||||
# package # low high
|
||||
|
||||
'alembic', # 0.3.1
|
||||
'SQLAlchemy', # 0.7.6
|
||||
# 'Tempita', # 0.5.1
|
||||
],
|
||||
|
@ -212,8 +208,6 @@ setup(
|
|||
|
||||
install_requires = requires,
|
||||
extras_require = extras,
|
||||
tests_require = requires + ['nose'],
|
||||
test_suite = 'nose.collector',
|
||||
|
||||
packages = find_packages(),
|
||||
include_package_data = True,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue