Upgraded to DRF2.4.x

This commit is contained in:
Marc Aymerich 2015-02-24 09:34:26 +00:00
parent 1fe98f434d
commit 1ed44bc745
20 changed files with 91 additions and 117 deletions

11
TODO.md
View file

@ -166,7 +166,7 @@
* webapp compat webapp-options
* 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)
@ -181,13 +181,10 @@ Multi-tenant WebApps
* 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?
* chomod user:group
* username maximum as group user in UNIX
* forms autocomplete="off"

View file

@ -100,7 +100,7 @@ class SendEmail(object):
'content_message': _(
"Are you sure you want to send the following message to the following %s?"
) % 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,
'subject': subject,
'message': message,

View file

@ -1,82 +1,15 @@
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import autodiscover_modules
from rest_framework.routers import DefaultRouter, Route, flatten, replace_methodname
from orchestra import settings
#from orchestra.utils.apps import autodiscover as module_autodiscover
from orchestra.utils.python import import_class
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):
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):
""" returns the root view, with all the linked collections """
APIRoot = import_class(settings.API_ROOT_VIEW)
@ -110,6 +43,6 @@ class LinkHeaderRouter(DefaultRouter):
# 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'))

View file

@ -17,8 +17,8 @@ class APIRoot(views.APIView):
'<%s>; rel="%s"' % (token_url, 'api-get-auth-token'),
]
body = {
'accountancy': [],
'services': [],
'accountancy': {},
'services': {},
}
if not request.user.is_anonymous():
list_name = '{basename}-list'
@ -44,12 +44,11 @@ class APIRoot(views.APIView):
group = 'accountancy'
menu = accounts[model].menu
if group and menu:
body[group].append({
body[group][basename] = {
'url': url,
'name': basename,
'verbose_name': model._meta.verbose_name,
'verbose_name_plural': model._meta.verbose_name_plural,
})
}
headers = {
'Link': ', '.join(links)
}

View file

@ -1,12 +1,29 @@
from django.http import HttpResponse
from rest_framework import viewsets
from rest_framework.decorators import detail_route
from orchestra.api import router
from orchestra.apps.accounts.api import AccountApiMixin
from orchestra.utils.html import html_to_pdf
from .models import Bill
from .serializers import BillSerializer
class BillViewSet(AccountApiMixin, viewsets.ModelViewSet):
model = Bill
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)

View file

@ -14,13 +14,14 @@ class BillLineSerializer(serializers.HyperlinkedModelSerializer):
class BillSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
lines = BillLineSerializer(source='billlines')
# lines = BillLineSerializer(source='lines')
class Meta:
model = Bill
fields = (
'url', 'number', 'type', 'total', 'is_sent', 'created_on', 'due_on',
'comments', 'html', 'lines'
'comments',
# 'lines'
)

View file

@ -38,6 +38,7 @@ class MySQLBackend(ServiceController):
return
context = self.get_context(database)
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):
self.append("mysql -e 'FLUSH PRIVILEGES;'")

View file

@ -36,7 +36,7 @@ class PasswdVirtualUserBackend(ServiceController):
fi""" % 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):
self.append(textwrap.dedent("""

View file

@ -25,7 +25,7 @@ class SystemUserBackend(ServiceController):
useradd %(username)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
fi
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:
context['member'] = member

View file

@ -21,8 +21,13 @@ class SystemUserQuerySet(models.QuerySet):
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."),
validators=[validators.validate_username])
password = models.CharField(_("password"), max_length=128)

View file

@ -18,7 +18,7 @@ class WebAppServiceMixin(object):
path="${path}/${dir}"
[ -d $path ] || {
mkdir "${path}"
chown %(user)s.%(group)s "${path}"
chown %(user)s:%(group)s "${path}"
}
done
""" % context))

View file

@ -27,7 +27,7 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
echo -e '%(wrapper_content)s' > %(wrapper_path)s; UPDATED_APACHE=1
}""" % 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):
if not self.valid_directive(webapp):

View file

@ -124,6 +124,12 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTIO
WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
# { name: ( verbose_name, [help_text], validation_regex ) }
# Filesystem
'public-root': (
_("Public root"),
_("Document root relative to webapps/&lt;webapp&gt;/"),
r'[^ ]+',
),
# Processes
'timeout': (
_("Process timeout"),
@ -220,6 +226,12 @@ WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
"(Integer between 0 and 999)."),
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"),
_("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)$'
),
'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."),
r'^[0-9]{1,4}$'
),

View file

@ -17,7 +17,7 @@ class WebalizerBackend(ServiceController):
self.append("[[ ! -e %(webalizer_path)s/index.html ]] && "
"echo 'Webstats are coming soon' > %(webalizer_path)s/index.html" % 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):
context = self.get_context(content)

View file

@ -30,7 +30,7 @@ class Website(models.Model):
@property
def unique_name(self):
return "%s-%s" % (self.account, self.name)
return "%s-%i" % (self.name, self.pk)
@cached
def get_options(self):

View file

@ -26,8 +26,13 @@ WEBSITES_OPTIONS = getattr(settings, 'WEBSITES_OPTIONS', {
),
'redirect': (
_("HTTPD - Redirection"),
_("<tt>[permanent] &lt;website path&gt; &lt;destination URL&gt;</tt>"),
r'^(permanent\s[^ ]+|[^ ]+)\s[^ ]+$',
_("<tt>&lt;website path&gt; &lt;destination URL&gt;</tt>"),
r'^[^ ]+\s[^ ]+$',
),
'proxy': (
_("HTTPD - Proxy"),
_("<tt>&lt;website path&gt; &lt;target URL&gt;</tt>"),
r'^[^ ]+\shttp[^ ]+(timeout=[0-9]{1,3}|retry=[0-9]|\s)*$',
),
'ssl_ca': (
"HTTPD - SSL CA",

View file

@ -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
# 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
# Django debug toolbar

View file

@ -1,5 +1,5 @@
{% extends "rest_framework/base.html" %}
{% load rest_framework utils %}
{% load rest_framework utils staticfiles %}
{% block head %}
{{ block.super }}
@ -17,7 +17,7 @@
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>{% optional_logout request %}</li>
<li>{% optional_logout request user %}</li>
</ul>
</li>
{% else %}

View file

@ -6,6 +6,6 @@ def html_to_pdf(html):
return run(
'PATH=$PATH:/usr/local/bin/\n'
'xvfb-run -a -s "-screen 0 640x4800x16" '
'wkhtmltopdf --footer-center "Page [page] of [topage]" --footer-font-size 9 - -',
stdin=html.encode('utf-8'), display=False
)
'wkhtmltopdf -q --footer-center "Page [page] of [topage]" --footer-font-size 9 - -',
stdin=html.encode('utf-8'), force_unicode=False
).stdout

View file

@ -21,11 +21,10 @@ def check_root(func):
return wrapped
class _AttributeUnicode(unicode):
class _Attribute(object):
""" Simple string subclass to allow arbitrary attribute access. """
@property
def stdout(self):
return unicode(self)
def __init__(self, stdout):
self.stdout = stdout
def make_async(fd):
@ -46,7 +45,7 @@ def read_async(fd):
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 """
if display:
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)
# Async reading of stdout and sterr
# TODO cleanup
while True:
# TODO https://github.com/isagalaev/ijson/issues/15
stdout = unicode()
sdterr = unicode()
stdout = unicode() if force_unicode else ''
sdterr = unicode() if force_unicode else ''
# Get complete unicode chunks
while True:
select.select([p.stdout, p.stderr], [], [])
stdoutPiece = read_async(p.stdout)
stderrPiece = read_async(p.stderr)
try:
stdout += stdoutPiece.decode("utf8")
sdterr += stderrPiece.decode("utf8")
except UnicodeDecodeError:
stdout += unicode(stdoutPiece.decode("utf8")) if force_unicode else stdoutPiece
sdterr += unicode(stderrPiece.decode("utf8")) if force_unicode else stderrPiece
except UnicodeDecodeError, e:
pass
else:
break
if display and stdout:
sys.stdout.write(stdout)
if display and stderrPiece:
if display and stderr:
sys.stderr.write(stderr)
state = _AttributeUnicode(stdout)
state = _Attribute(stdout)
state.stderr = sdterr
state.return_code = p.poll()
yield state
@ -95,8 +94,8 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin='')
raise StopIteration
def run(command, display=False, error_codes=[0], silent=False, stdin='', async=False):
iterator = runiterator(command, display, error_codes, silent, stdin)
def run(command, display=False, error_codes=[0], silent=False, stdin='', async=False, force_unicode=True):
iterator = runiterator(command, display, error_codes, silent, stdin, force_unicode)
iterator.next()
if async:
return iterator
@ -109,7 +108,7 @@ def run(command, display=False, error_codes=[0], silent=False, stdin='', async=F
return_code = state.return_code
out = _AttributeUnicode(stdout.strip())
out = _Attribute(stdout.strip())
err = stderr.strip()
out.failed = False