Upgraded to DRF2.4.x
This commit is contained in:
parent
1fe98f434d
commit
1ed44bc745
11
TODO.md
11
TODO.md
|
@ -166,7 +166,7 @@
|
||||||
* webapp compat webapp-options
|
* webapp compat webapp-options
|
||||||
* webapps modeled on classes instead of settings?
|
* webapps modeled on classes instead of settings?
|
||||||
|
|
||||||
* Change account and orders
|
* Service.account change and orders consistency
|
||||||
|
|
||||||
* Mix webapps type with backends (two for the price of one)
|
* Mix webapps type with backends (two for the price of one)
|
||||||
|
|
||||||
|
@ -181,13 +181,10 @@ Multi-tenant WebApps
|
||||||
|
|
||||||
* Howto upgrade webapp PHP version? <FilesMatch \.php$> SetHandler php54-cgi</FilesMatch> ? or create a new app
|
* Howto upgrade webapp PHP version? <FilesMatch \.php$> SetHandler php54-cgi</FilesMatch> ? or create a new app
|
||||||
|
|
||||||
* prevent @pangea.org email addresses on contacts
|
* prevent @pangea.org email addresses on contacts, enforce at least one email without @pangea.org
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* fcgid kill instead of apache reload?
|
* fcgid kill instead of apache reload?
|
||||||
|
|
||||||
|
|
||||||
* chomod user:group
|
|
||||||
* username maximum as group user in UNIX
|
* username maximum as group user in UNIX
|
||||||
|
|
||||||
|
* forms autocomplete="off"
|
||||||
|
|
|
@ -100,7 +100,7 @@ class SendEmail(object):
|
||||||
'content_message': _(
|
'content_message': _(
|
||||||
"Are you sure you want to send the following message to the following %s?"
|
"Are you sure you want to send the following message to the following %s?"
|
||||||
) % self.opts.verbose_name_plural,
|
) % self.opts.verbose_name_plural,
|
||||||
'display_objects': ["%s (%s)" % (contact, contact.email) for contact in self.queryset],
|
'display_objects': [u"%s (%s)" % (contact, contact.email) for contact in self.queryset],
|
||||||
'form': form,
|
'form': form,
|
||||||
'subject': subject,
|
'subject': subject,
|
||||||
'message': message,
|
'message': message,
|
||||||
|
|
|
@ -1,82 +1,15 @@
|
||||||
|
from django.conf import settings as django_settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.utils.module_loading import autodiscover_modules
|
from django.utils.module_loading import autodiscover_modules
|
||||||
from rest_framework.routers import DefaultRouter, Route, flatten, replace_methodname
|
from rest_framework.routers import DefaultRouter, Route, flatten, replace_methodname
|
||||||
|
|
||||||
from orchestra import settings
|
from orchestra import settings
|
||||||
#from orchestra.utils.apps import autodiscover as module_autodiscover
|
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
from .helpers import insert_links, replace_collectionmethodname
|
from .helpers import insert_links, replace_collectionmethodname
|
||||||
|
|
||||||
|
|
||||||
def collectionlink(**kwargs):
|
|
||||||
"""
|
|
||||||
Used to mark a method on a ViewSet collection that should be routed for GET requests.
|
|
||||||
"""
|
|
||||||
# TODO deprecate in favour of DRF2.0 own method
|
|
||||||
def decorator(func):
|
|
||||||
func.collection_bind_to_methods = ['get']
|
|
||||||
func.kwargs = kwargs
|
|
||||||
return func
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
class LinkHeaderRouter(DefaultRouter):
|
class LinkHeaderRouter(DefaultRouter):
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
""" collection view method route """
|
|
||||||
super(LinkHeaderRouter, self).__init__(*args, **kwargs)
|
|
||||||
self.routes.insert(0, Route(
|
|
||||||
url=r'^{prefix}/{collectionmethodname}{trailing_slash}$',
|
|
||||||
mapping={
|
|
||||||
'{httpmethod}': '{collectionmethodname}',
|
|
||||||
},
|
|
||||||
name='{basename}-{methodnamehyphen}',
|
|
||||||
initkwargs={}
|
|
||||||
))
|
|
||||||
|
|
||||||
def get_routes(self, viewset):
|
|
||||||
""" allow links and actions to be bound to a collection view """
|
|
||||||
known_actions = flatten([route.mapping.values() for route in self.routes])
|
|
||||||
dynamic_routes = []
|
|
||||||
collection_dynamic_routes = []
|
|
||||||
for methodname in dir(viewset):
|
|
||||||
attr = getattr(viewset, methodname)
|
|
||||||
bind = getattr(attr, 'bind_to_methods', None)
|
|
||||||
httpmethods = getattr(attr, 'collection_bind_to_methods', bind)
|
|
||||||
if httpmethods:
|
|
||||||
if methodname in known_actions:
|
|
||||||
msg = ('Cannot use @action or @link decorator on method "%s" '
|
|
||||||
'as it is an existing route' % methodname)
|
|
||||||
raise ImproperlyConfigured(msg)
|
|
||||||
httpmethods = [method.lower() for method in httpmethods]
|
|
||||||
if bind:
|
|
||||||
dynamic_routes.append((httpmethods, methodname))
|
|
||||||
else:
|
|
||||||
collection_dynamic_routes.append((httpmethods, methodname))
|
|
||||||
|
|
||||||
ret = []
|
|
||||||
for route in self.routes:
|
|
||||||
# Dynamic routes (@link or @action decorator)
|
|
||||||
if route.mapping == {'{httpmethod}': '{methodname}'}:
|
|
||||||
replace = replace_methodname
|
|
||||||
routes = dynamic_routes
|
|
||||||
elif route.mapping == {'{httpmethod}': '{collectionmethodname}'}:
|
|
||||||
replace = replace_collectionmethodname
|
|
||||||
routes = collection_dynamic_routes
|
|
||||||
else:
|
|
||||||
ret.append(route)
|
|
||||||
continue
|
|
||||||
for httpmethods, methodname in routes:
|
|
||||||
initkwargs = route.initkwargs.copy()
|
|
||||||
initkwargs.update(getattr(viewset, methodname).kwargs)
|
|
||||||
ret.append(Route(
|
|
||||||
url=replace(route.url, methodname),
|
|
||||||
mapping={ httpmethod: methodname for httpmethod in httpmethods },
|
|
||||||
name=replace(route.name, methodname),
|
|
||||||
initkwargs=initkwargs,
|
|
||||||
))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def get_api_root_view(self):
|
def get_api_root_view(self):
|
||||||
""" returns the root view, with all the linked collections """
|
""" returns the root view, with all the linked collections """
|
||||||
APIRoot = import_class(settings.API_ROOT_VIEW)
|
APIRoot = import_class(settings.API_ROOT_VIEW)
|
||||||
|
@ -110,6 +43,6 @@ class LinkHeaderRouter(DefaultRouter):
|
||||||
|
|
||||||
|
|
||||||
# Create a router and register our viewsets with it.
|
# Create a router and register our viewsets with it.
|
||||||
router = LinkHeaderRouter()
|
router = LinkHeaderRouter(trailing_slash=django_settings.APPEND_SLASH)
|
||||||
|
|
||||||
autodiscover = lambda: (autodiscover_modules('api'), autodiscover_modules('serializers'))
|
autodiscover = lambda: (autodiscover_modules('api'), autodiscover_modules('serializers'))
|
||||||
|
|
|
@ -17,8 +17,8 @@ class APIRoot(views.APIView):
|
||||||
'<%s>; rel="%s"' % (token_url, 'api-get-auth-token'),
|
'<%s>; rel="%s"' % (token_url, 'api-get-auth-token'),
|
||||||
]
|
]
|
||||||
body = {
|
body = {
|
||||||
'accountancy': [],
|
'accountancy': {},
|
||||||
'services': [],
|
'services': {},
|
||||||
}
|
}
|
||||||
if not request.user.is_anonymous():
|
if not request.user.is_anonymous():
|
||||||
list_name = '{basename}-list'
|
list_name = '{basename}-list'
|
||||||
|
@ -44,12 +44,11 @@ class APIRoot(views.APIView):
|
||||||
group = 'accountancy'
|
group = 'accountancy'
|
||||||
menu = accounts[model].menu
|
menu = accounts[model].menu
|
||||||
if group and menu:
|
if group and menu:
|
||||||
body[group].append({
|
body[group][basename] = {
|
||||||
'url': url,
|
'url': url,
|
||||||
'name': basename,
|
|
||||||
'verbose_name': model._meta.verbose_name,
|
'verbose_name': model._meta.verbose_name,
|
||||||
'verbose_name_plural': model._meta.verbose_name_plural,
|
'verbose_name_plural': model._meta.verbose_name_plural,
|
||||||
})
|
}
|
||||||
headers = {
|
headers = {
|
||||||
'Link': ', '.join(links)
|
'Link': ', '.join(links)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
|
from django.http import HttpResponse
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.decorators import detail_route
|
||||||
|
|
||||||
from orchestra.api import router
|
from orchestra.api import router
|
||||||
from orchestra.apps.accounts.api import AccountApiMixin
|
from orchestra.apps.accounts.api import AccountApiMixin
|
||||||
|
from orchestra.utils.html import html_to_pdf
|
||||||
|
|
||||||
from .models import Bill
|
from .models import Bill
|
||||||
from .serializers import BillSerializer
|
from .serializers import BillSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BillViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
class BillViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Bill
|
model = Bill
|
||||||
serializer_class = BillSerializer
|
serializer_class = BillSerializer
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def document(self, request, pk):
|
||||||
|
bill = self.get_object()
|
||||||
|
content_type = request.META.get('HTTP_ACCEPT')
|
||||||
|
if content_type == 'application/pdf':
|
||||||
|
pdf = html_to_pdf(bill.html or bill.render())
|
||||||
|
return HttpResponse(pdf, content_type='application/pdf')
|
||||||
|
else:
|
||||||
|
return HttpResponse(bill.html or bill.render())
|
||||||
|
|
||||||
|
|
||||||
|
router.register('bills', BillViewSet)
|
||||||
|
|
|
@ -14,13 +14,14 @@ class BillLineSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class BillSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
class BillSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||||
lines = BillLineSerializer(source='billlines')
|
# lines = BillLineSerializer(source='lines')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Bill
|
model = Bill
|
||||||
fields = (
|
fields = (
|
||||||
'url', 'number', 'type', 'total', 'is_sent', 'created_on', 'due_on',
|
'url', 'number', 'type', 'total', 'is_sent', 'created_on', 'due_on',
|
||||||
'comments', 'html', 'lines'
|
'comments',
|
||||||
|
# 'lines'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ class MySQLBackend(ServiceController):
|
||||||
return
|
return
|
||||||
context = self.get_context(database)
|
context = self.get_context(database)
|
||||||
self.append("mysql -e 'DROP DATABASE `%(database)s`;'" % context)
|
self.append("mysql -e 'DROP DATABASE `%(database)s`;'" % context)
|
||||||
|
self.append("mysql mysql -e 'DELETE FROM db WHERE db = `%(database)s`;'" % context)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
self.append("mysql -e 'FLUSH PRIVILEGES;'")
|
self.append("mysql -e 'FLUSH PRIVILEGES;'")
|
||||||
|
|
|
@ -36,7 +36,7 @@ class PasswdVirtualUserBackend(ServiceController):
|
||||||
fi""" % context
|
fi""" % context
|
||||||
))
|
))
|
||||||
self.append("mkdir -p %(home)s" % context)
|
self.append("mkdir -p %(home)s" % context)
|
||||||
self.append("chown %(uid)s.%(gid)s %(home)s" % context)
|
self.append("chown %(uid)s:%(gid)s %(home)s" % context)
|
||||||
|
|
||||||
def set_mailbox(self, context):
|
def set_mailbox(self, context):
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
|
|
|
@ -25,7 +25,7 @@ class SystemUserBackend(ServiceController):
|
||||||
useradd %(username)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
useradd %(username)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||||
fi
|
fi
|
||||||
mkdir -p %(home)s
|
mkdir -p %(home)s
|
||||||
chown %(username)s.%(username)s %(home)s""" % context
|
chown %(username)s:%(username)s %(home)s""" % context
|
||||||
))
|
))
|
||||||
for member in settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS:
|
for member in settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS:
|
||||||
context['member'] = member
|
context['member'] = member
|
||||||
|
|
|
@ -21,8 +21,13 @@ class SystemUserQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
|
||||||
class SystemUser(models.Model):
|
class SystemUser(models.Model):
|
||||||
""" System users """
|
"""
|
||||||
username = models.CharField(_("username"), max_length=64, unique=True,
|
System users
|
||||||
|
|
||||||
|
Username max_length determined by min(user, group) on common LINUX systems; min(32, 16)
|
||||||
|
"""
|
||||||
|
# TODO max_length
|
||||||
|
username = models.CharField(_("username"), max_length=32, unique=True,
|
||||||
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
|
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
|
||||||
validators=[validators.validate_username])
|
validators=[validators.validate_username])
|
||||||
password = models.CharField(_("password"), max_length=128)
|
password = models.CharField(_("password"), max_length=128)
|
||||||
|
|
|
@ -18,7 +18,7 @@ class WebAppServiceMixin(object):
|
||||||
path="${path}/${dir}"
|
path="${path}/${dir}"
|
||||||
[ -d $path ] || {
|
[ -d $path ] || {
|
||||||
mkdir "${path}"
|
mkdir "${path}"
|
||||||
chown %(user)s.%(group)s "${path}"
|
chown %(user)s:%(group)s "${path}"
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
""" % context))
|
""" % context))
|
||||||
|
|
|
@ -27,7 +27,7 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
||||||
echo -e '%(wrapper_content)s' > %(wrapper_path)s; UPDATED_APACHE=1
|
echo -e '%(wrapper_content)s' > %(wrapper_path)s; UPDATED_APACHE=1
|
||||||
}""" % context))
|
}""" % context))
|
||||||
self.append("chmod +x %(wrapper_path)s" % context)
|
self.append("chmod +x %(wrapper_path)s" % context)
|
||||||
self.append("chown -R %(user)s.%(group)s %(wrapper_dir)s" % context)
|
self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context)
|
||||||
|
|
||||||
def delete(self, webapp):
|
def delete(self, webapp):
|
||||||
if not self.valid_directive(webapp):
|
if not self.valid_directive(webapp):
|
||||||
|
|
|
@ -124,6 +124,12 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTIO
|
||||||
|
|
||||||
WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
||||||
# { name: ( verbose_name, [help_text], validation_regex ) }
|
# { name: ( verbose_name, [help_text], validation_regex ) }
|
||||||
|
# Filesystem
|
||||||
|
'public-root': (
|
||||||
|
_("Public root"),
|
||||||
|
_("Document root relative to webapps/<webapp>/"),
|
||||||
|
r'[^ ]+',
|
||||||
|
),
|
||||||
# Processes
|
# Processes
|
||||||
'timeout': (
|
'timeout': (
|
||||||
_("Process timeout"),
|
_("Process timeout"),
|
||||||
|
@ -220,6 +226,12 @@ WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
||||||
"(Integer between 0 and 999)."),
|
"(Integer between 0 and 999)."),
|
||||||
r'^[0-9]{1,3}$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
|
'PHP-max_input_vars': (
|
||||||
|
_("PHP - Max input vars"),
|
||||||
|
_("How many input variables may be accepted (limit is applied to $_GET, $_POST and $_COOKIE superglobal separately) "
|
||||||
|
"(Integer between 0 and 9999)."),
|
||||||
|
r'^[0-9]{1,4}$'
|
||||||
|
),
|
||||||
'PHP-memory_limit': (
|
'PHP-memory_limit': (
|
||||||
_("PHP - Memory limit"),
|
_("PHP - Memory limit"),
|
||||||
_("This sets the maximum amount of memory in bytes that a script is allowed to allocate "
|
_("This sets the maximum amount of memory in bytes that a script is allowed to allocate "
|
||||||
|
@ -269,7 +281,12 @@ WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
||||||
r'^(On|Off|on|off)$'
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
'PHP-suhosin.post.max_vars': (
|
'PHP-suhosin.post.max_vars': (
|
||||||
_("PHP - Suhosin post max vars"),
|
_("PHP - Suhosin POST max vars"),
|
||||||
|
_("Number between 0 and 9999."),
|
||||||
|
r'^[0-9]{1,4}$'
|
||||||
|
),
|
||||||
|
'PHP-suhosin.get.max_vars': (
|
||||||
|
_("PHP - Suhosin GET max vars"),
|
||||||
_("Number between 0 and 9999."),
|
_("Number between 0 and 9999."),
|
||||||
r'^[0-9]{1,4}$'
|
r'^[0-9]{1,4}$'
|
||||||
),
|
),
|
||||||
|
|
|
@ -17,7 +17,7 @@ class WebalizerBackend(ServiceController):
|
||||||
self.append("[[ ! -e %(webalizer_path)s/index.html ]] && "
|
self.append("[[ ! -e %(webalizer_path)s/index.html ]] && "
|
||||||
"echo 'Webstats are coming soon' > %(webalizer_path)s/index.html" % context)
|
"echo 'Webstats are coming soon' > %(webalizer_path)s/index.html" % context)
|
||||||
self.append("echo '%(webalizer_conf)s' > %(webalizer_conf_path)s" % context)
|
self.append("echo '%(webalizer_conf)s' > %(webalizer_conf_path)s" % context)
|
||||||
self.append("chown %(user)s.www-data %(webalizer_path)s" % context)
|
self.append("chown %(user)s:www-data %(webalizer_path)s" % context)
|
||||||
|
|
||||||
def delete(self, content):
|
def delete(self, content):
|
||||||
context = self.get_context(content)
|
context = self.get_context(content)
|
||||||
|
|
|
@ -30,7 +30,7 @@ class Website(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_name(self):
|
def unique_name(self):
|
||||||
return "%s-%s" % (self.account, self.name)
|
return "%s-%i" % (self.name, self.pk)
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
|
|
|
@ -26,8 +26,13 @@ WEBSITES_OPTIONS = getattr(settings, 'WEBSITES_OPTIONS', {
|
||||||
),
|
),
|
||||||
'redirect': (
|
'redirect': (
|
||||||
_("HTTPD - Redirection"),
|
_("HTTPD - Redirection"),
|
||||||
_("<tt>[permanent] <website path> <destination URL></tt>"),
|
_("<tt><website path> <destination URL></tt>"),
|
||||||
r'^(permanent\s[^ ]+|[^ ]+)\s[^ ]+$',
|
r'^[^ ]+\s[^ ]+$',
|
||||||
|
),
|
||||||
|
'proxy': (
|
||||||
|
_("HTTPD - Proxy"),
|
||||||
|
_("<tt><website path> <target URL></tt>"),
|
||||||
|
r'^[^ ]+\shttp[^ ]+(timeout=[0-9]{1,3}|retry=[0-9]|\s)*$',
|
||||||
),
|
),
|
||||||
'ssl_ca': (
|
'ssl_ca': (
|
||||||
"HTTPD - SSL CA",
|
"HTTPD - SSL CA",
|
||||||
|
|
|
@ -11,7 +11,7 @@ CELERY_SEND_TASK_ERROR_EMAILS = False
|
||||||
|
|
||||||
# When DEBUG is enabled Django appends every executed SQL statement to django.db.connection.queries
|
# When DEBUG is enabled Django appends every executed SQL statement to django.db.connection.queries
|
||||||
# this will grow unbounded in a long running process environment like celeryd
|
# this will grow unbounded in a long running process environment like celeryd
|
||||||
if "celeryd" in sys.argv or 'celeryev' in sys.argv or 'celerybeat' in sys.argv:
|
if set(('celeryd', 'celeryev', 'celerycam', 'celerybeat')).intersection(sys.argv):
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
# Django debug toolbar
|
# Django debug toolbar
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "rest_framework/base.html" %}
|
{% extends "rest_framework/base.html" %}
|
||||||
{% load rest_framework utils %}
|
{% load rest_framework utils staticfiles %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
<b class="caret"></b>
|
<b class="caret"></b>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>{% optional_logout request %}</li>
|
<li>{% optional_logout request user %}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -6,6 +6,6 @@ def html_to_pdf(html):
|
||||||
return run(
|
return run(
|
||||||
'PATH=$PATH:/usr/local/bin/\n'
|
'PATH=$PATH:/usr/local/bin/\n'
|
||||||
'xvfb-run -a -s "-screen 0 640x4800x16" '
|
'xvfb-run -a -s "-screen 0 640x4800x16" '
|
||||||
'wkhtmltopdf --footer-center "Page [page] of [topage]" --footer-font-size 9 - -',
|
'wkhtmltopdf -q --footer-center "Page [page] of [topage]" --footer-font-size 9 - -',
|
||||||
stdin=html.encode('utf-8'), display=False
|
stdin=html.encode('utf-8'), force_unicode=False
|
||||||
)
|
).stdout
|
||||||
|
|
|
@ -21,11 +21,10 @@ def check_root(func):
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class _AttributeUnicode(unicode):
|
class _Attribute(object):
|
||||||
""" Simple string subclass to allow arbitrary attribute access. """
|
""" Simple string subclass to allow arbitrary attribute access. """
|
||||||
@property
|
def __init__(self, stdout):
|
||||||
def stdout(self):
|
self.stdout = stdout
|
||||||
return unicode(self)
|
|
||||||
|
|
||||||
|
|
||||||
def make_async(fd):
|
def make_async(fd):
|
||||||
|
@ -46,7 +45,7 @@ def read_async(fd):
|
||||||
return u''
|
return u''
|
||||||
|
|
||||||
|
|
||||||
def runiterator(command, display=False, error_codes=[0], silent=False, stdin=''):
|
def runiterator(command, display=False, error_codes=[0], silent=False, stdin='', force_unicode=True):
|
||||||
""" Subprocess wrapper for running commands concurrently """
|
""" Subprocess wrapper for running commands concurrently """
|
||||||
if display:
|
if display:
|
||||||
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
|
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
|
||||||
|
@ -62,29 +61,29 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin='')
|
||||||
make_async(p.stderr)
|
make_async(p.stderr)
|
||||||
|
|
||||||
# Async reading of stdout and sterr
|
# Async reading of stdout and sterr
|
||||||
|
# TODO cleanup
|
||||||
while True:
|
while True:
|
||||||
# TODO https://github.com/isagalaev/ijson/issues/15
|
# TODO https://github.com/isagalaev/ijson/issues/15
|
||||||
stdout = unicode()
|
stdout = unicode() if force_unicode else ''
|
||||||
sdterr = unicode()
|
sdterr = unicode() if force_unicode else ''
|
||||||
# Get complete unicode chunks
|
# Get complete unicode chunks
|
||||||
while True:
|
while True:
|
||||||
select.select([p.stdout, p.stderr], [], [])
|
select.select([p.stdout, p.stderr], [], [])
|
||||||
stdoutPiece = read_async(p.stdout)
|
stdoutPiece = read_async(p.stdout)
|
||||||
stderrPiece = read_async(p.stderr)
|
stderrPiece = read_async(p.stderr)
|
||||||
try:
|
try:
|
||||||
stdout += stdoutPiece.decode("utf8")
|
stdout += unicode(stdoutPiece.decode("utf8")) if force_unicode else stdoutPiece
|
||||||
sdterr += stderrPiece.decode("utf8")
|
sdterr += unicode(stderrPiece.decode("utf8")) if force_unicode else stderrPiece
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError, e:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
if display and stdout:
|
if display and stdout:
|
||||||
sys.stdout.write(stdout)
|
sys.stdout.write(stdout)
|
||||||
if display and stderrPiece:
|
if display and stderr:
|
||||||
sys.stderr.write(stderr)
|
sys.stderr.write(stderr)
|
||||||
|
|
||||||
state = _AttributeUnicode(stdout)
|
state = _Attribute(stdout)
|
||||||
state.stderr = sdterr
|
state.stderr = sdterr
|
||||||
state.return_code = p.poll()
|
state.return_code = p.poll()
|
||||||
yield state
|
yield state
|
||||||
|
@ -95,8 +94,8 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin='')
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
|
|
||||||
def run(command, display=False, error_codes=[0], silent=False, stdin='', async=False):
|
def run(command, display=False, error_codes=[0], silent=False, stdin='', async=False, force_unicode=True):
|
||||||
iterator = runiterator(command, display, error_codes, silent, stdin)
|
iterator = runiterator(command, display, error_codes, silent, stdin, force_unicode)
|
||||||
iterator.next()
|
iterator.next()
|
||||||
if async:
|
if async:
|
||||||
return iterator
|
return iterator
|
||||||
|
@ -109,7 +108,7 @@ def run(command, display=False, error_codes=[0], silent=False, stdin='', async=F
|
||||||
|
|
||||||
return_code = state.return_code
|
return_code = state.return_code
|
||||||
|
|
||||||
out = _AttributeUnicode(stdout.strip())
|
out = _Attribute(stdout.strip())
|
||||||
err = stderr.strip()
|
err = stderr.strip()
|
||||||
|
|
||||||
out.failed = False
|
out.failed = False
|
||||||
|
|
Loading…
Reference in New Issue