Improved variable names consistency, cleaned up *settings.py and refactored resource aggregation

This commit is contained in:
Marc Aymerich 2015-03-31 12:39:08 +00:00
parent bcfc453a95
commit 9e59346042
64 changed files with 839 additions and 369 deletions

57
TODO.md
View File

@ -20,7 +20,7 @@
* backend logs with hal logo * backend logs with hal logo
* LAST version of this shit http://wkhtmltopdf.org/downloads.html * LAST version of this shit http://wkhtmltopdf.org/downloads.h otml
* translations * translations
from django.utils import translation from django.utils import translation
@ -137,17 +137,15 @@
* Resource graph for each related object * Resource graph for each related object
* Service.account change and orders consistency
* SaaS model splitted into SaaSUser and SaaSSite? inherit from SaaS * SaaS model splitted into SaaSUser and SaaSSite? inherit from SaaS
* prevent @pangea.org email addresses on contacts, enforce at least one email without @pangea.org * prevent @pangea.org email addresses on contacts, enforce at least one email without @pangea.org
* forms autocomplete="off", doesn't work in chrome * forms autocomplete="off", doesn't work in chrome
ln -s /proc/self/fd /dev/fd ln -s /proc/self/fd /dev/fd
* escape passwords and not allow ' on them !
POST INSTALL POST INSTALL
@ -160,15 +158,12 @@ ssh-copy-id root@<server-address>
Php binaries should have this format: /usr/bin/php5.2-cgi Php binaries should have this format: /usr/bin/php5.2-cgi
* logs on panel/logs/ ? mkdir ~webapps, backend post save signal? * logs on panel/logs/ ? mkdir ~webapps, backend post save signal?
* transaction fault tolerant on backend.execute() * transaction fault tolerant on backend.execute()
* <IfModule security2_module> and other IfModule on backend SecRule * <IfModule security2_module> and other IfModule on backend SecRule
* Orchestra global search box on the page head, based https://github.com/django/django/blob/master/django/contrib/admin/options.py#L866 and iterating over all registered services and inspectin its admin.search_fields * Orchestra global search box on the page head, based https://github.com/django/django/blob/master/django/contrib/admin/options.py#L866 and iterating over all registered services and inspectin its admin.search_fields
* contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently, perhaps fail at starttime? apploading machinary * contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently, perhaps fail at starttime? apploading machinary
* contact.alternative_phone on a phone.tooltip, email:to * contact.alternative_phone on a phone.tooltip, email:to
@ -223,7 +218,7 @@ require_once(/etc/moodles/.$moodle_host.config.php);``` moodle/drupl
* autoexpand mailbox.filter according to filtering options * autoexpand mailbox.filter according to filtering options
* allow empty metric pack for default rates? changes on rating algo * allow empty metric pack for default rates? changes on rating algo
* IMPORTANT make sure no order is created for mailboxes that include disk? or just don't produce lines with cost == 0 or quantity 0 ? * IMPORTANT make sure no order is created for mailboxes that include disk? or just don't produce lines with cost == 0 or quantity 0 ? maybe minimal quantity for billing? like 0.1 ? or minimal price? per line or per bill?
* Improve performance of admin change lists with debug toolbar and prefech_related * Improve performance of admin change lists with debug toolbar and prefech_related
* and miscellaneous.service.name == 'domini-registre' * and miscellaneous.service.name == 'domini-registre'
@ -231,39 +226,44 @@ require_once(/etc/moodles/.$moodle_host.config.php);``` moodle/drupl
* lines too long on invoice, double lines or cut, and make margin wider * lines too long on invoice, double lines or cut, and make margin wider
* PHP_TIMEOUT env variable in sync with fcgid idle timeout * PHP_TIMEOUT env variable in sync with fcgid idle timeout
http://foaa.de/old-blog/2010/11/php-apache-and-fastcgi-a-comprehensive-overview/trackback/index.html#pni-top0
* payment methods icons * payment methods icons
* use server.name | server.address on python backends, like gitlab instead of settings? * use server.name | server.address on python backends, like gitlab instead of settings?
* saas change password feature (the only way of re.running a backend) * saas change password feature (the only way of re.running a backend)
* TODO raise404, here and everywhere * TODO raise404, here and everywhere
* display subline links on billlines * display subline links on billlines, to show that they exists.
* update service orders on a celery task? * update service orders on a celery task? because it take alot
*
* billline quantity eval('10x100') instead of miningless description '(10*100)' * billline quantity eval('10x100') instead of miningless description '(10*100)'
* order metric increases inside billed until period * IMPORTANT do more test, make sure billed until doesn't get uodated whhen services are billed with les metric, and don't upgrade billed_until when undoing under this circumstances
* do more test, make sure billed until doesn't get uodated whhen services are billed with les metric, and don't upgrade billed_until when undoing under this circumstances * line 513: change threshold and one time service metric change should update last value if not billed, only record for recurring invoicing. postpay services should store the last metric for pricing period.
* add ini, end dates on bill lines and breakup quanity into size(defaut:1) and metric
* threshold for significative metric accountancy on services.handler
* move normurlpath to orchestra.utils from websites.utils * move normurlpath to orchestra.utils from websites.utils
* one time service metric change should update last value, only record for recurring invoicing.
* write down insights * write down insights
* pluggable rate algorithms, with help_text, and change some services to match price
* translation app, with generates the trans files from models
* use english on services defs and so on, an translate them on render time * use english on services defs and so on, an translate them on render time
* (miscellaneous.service.ident or '').startswith()
* websites directives get_location() and use it on last change view validation stage to compare with contents.location and also on the backend ?
* modeladmin Default filter + search isn't working, prepend filter when searching
* IMPORTANT do all modles.py TODOs and create migrations for finished apps
* create service templates based on urlqwargs with the most basic services.
* Base price: domini propi (all domains) + extra for other domains
Translation Translation
----------- -----------
python manage.py makemessages -l ca --domain database
mkdir locale mkdir locale
django-admin.py makemessages -l ca django-admin.py makemessages -l ca
django-admin.py compilemessages -l ca django-admin.py compilemessages -l ca
@ -273,5 +273,20 @@ https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#joining-strings-s
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils import translation from django.utils import translation
translation.activate('ca') translation.activate('ca')
ugettext("Fuck you") ugettext("Description")
Object = disk*15
bscw quota
root@web:/home/pangea/bscw/bin ./bsadmin quota report
Disk Objects
User usage soft hard time usage soft hard time
xxx2 -- 0 20M 22M 9 200 300
xxxxxxxxxxxxx -- 0 20M 22M 8 200 300
xxxxx -- 0 20M 22M 7 200 300
xxxxx -- 0 20M 22M 7 200 300
* saas validate_creation generic approach, for all backends. standard output
* html code x: &times;

View File

@ -1,6 +1,8 @@
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.settings import BASE_DOMAIN
ACCOUNTS_TYPES = getattr(settings, 'ACCOUNTS_TYPES', ( ACCOUNTS_TYPES = getattr(settings, 'ACCOUNTS_TYPES', (
('INDIVIDUAL', _("Individual")), ('INDIVIDUAL', _("Individual")),
@ -11,7 +13,10 @@ ACCOUNTS_TYPES = getattr(settings, 'ACCOUNTS_TYPES', (
('STAFF', _("Staff")), ('STAFF', _("Staff")),
)) ))
ACCOUNTS_DEFAULT_TYPE = getattr(settings, 'ACCOUNTS_DEFAULT_TYPE', 'INDIVIDUAL')
ACCOUNTS_DEFAULT_TYPE = getattr(settings, 'ACCOUNTS_DEFAULT_TYPE',
'INDIVIDUAL'
)
ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', ( ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
@ -20,13 +25,18 @@ ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
ACCOUNTS_SYSTEMUSER_MODEL = getattr(settings, 'ACCOUNTS_SYSTEMUSER_MODEL', ACCOUNTS_SYSTEMUSER_MODEL = getattr(settings, 'ACCOUNTS_SYSTEMUSER_MODEL',
'systemusers.SystemUser') 'systemusers.SystemUser'
)
ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'EN') ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE',
'EN'
)
ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK', 1) ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK',
1
)
ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', ( ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
@ -42,12 +52,13 @@ ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
('domains.Domain', ('domains.Domain',
'name', 'name',
{ {
'name': '"%s.orchestra.lan" % account.username.replace("_", "-")', 'name': '"%s.{}" % account.username.replace("_", "-")'.format(BASE_DOMAIN),
}, },
_("Designates whether to creates a related subdomain &lt;username&gt;.orchestra.lan or not."), _("Designates whether to creates a related subdomain &lt;username&gt;.{} or not.".format(BASE_DOMAIN)),
), ),
)) ))
ACCOUNTS_SERVICE_REPORT_TEMPLATE = getattr(settings, 'ACCOUNTS_SERVICE_REPORT_TEMPLATE', ACCOUNTS_SERVICE_REPORT_TEMPLATE = getattr(settings, 'ACCOUNTS_SERVICE_REPORT_TEMPLATE',
'admin/accounts/account/service_report.html') 'admin/accounts/account/service_report.html'
)

View File

@ -22,7 +22,7 @@
{% block object-tools-items %} {% block object-tools-items %}
{% if services %} {% if services %}
<li><select name="forma" onchange="location = this.options[this.selectedIndex].value;" style="margin: -3px 0 0 0;"> <li><select name="forma" onchange="location = this.options[this.selectedIndex].value;" style="margin: -3px 0 0 0;">
<option value="">{% trans "Services:" %}</option> <option selected disabled>{% trans "Services" %}</option>
{% for service in services %} {% for service in services %}
<option value="{% url service|admin_urlname:'changelist' %}?account={{ original.pk }}">{{ service.verbose_name_plural|capfirst }}</option> <option value="{% url service|admin_urlname:'changelist' %}?account={{ original.pk }}">{{ service.verbose_name_plural|capfirst }}</option>
{% endfor %} {% endfor %}
@ -30,7 +30,7 @@
{% endif %} {% endif %}
{% if accounts %} {% if accounts %}
<li><select name="forma" onchange="location = this.options[this.selectedIndex].value;" style="margin: -3px 4px 0px 4px;"> <li><select name="forma" onchange="location = this.options[this.selectedIndex].value;" style="margin: -3px 4px 0px 4px;">
<option value="">{% trans "Accounts:" %}</option> <option selected disabled>{% trans "Accounts" %}</option>
{% for account in accounts %} {% for account in accounts %}
<option value="{% url account|admin_urlname:'changelist' %}?account={{ original.pk }}">{{ account.verbose_name_plural|capfirst }}</option> <option value="{% url account|admin_urlname:'changelist' %}?account={{ original.pk }}">{{ account.verbose_name_plural|capfirst }}</option>
{% endfor %} {% endfor %}

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-03-29 10:17+0000\n" "POT-Creation-Date: 2015-03-29 20:21+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -20,11 +20,11 @@ msgstr ""
#: actions.py:35 #: actions.py:35
msgid "Download" msgid "Download"
msgstr "Blod" msgstr "Descarrega"
#: actions.py:45 #: actions.py:45
msgid "View" msgid "View"
msgstr "" msgstr "Vista"
#: actions.py:53 #: actions.py:53
msgid "Selected bills should be in open state" msgid "Selected bills should be in open state"
@ -62,11 +62,11 @@ msgstr ""
msgid "Resend" msgid "Resend"
msgstr "" msgstr ""
#: actions.py:129 models.py:308 #: actions.py:129 models.py:309
msgid "Not enough information stored for undoing" msgid "Not enough information stored for undoing"
msgstr "" msgstr ""
#: actions.py:132 models.py:310 #: actions.py:132 models.py:311
msgid "Dates don't match" msgid "Dates don't match"
msgstr "" msgstr ""
@ -92,7 +92,7 @@ msgstr ""
#: admin.py:69 #: admin.py:69
msgid "Description" msgid "Description"
msgstr "" msgstr "Descripció"
#: admin.py:77 #: admin.py:77
msgid "Subtotal" msgid "Subtotal"
@ -115,37 +115,37 @@ msgstr ""
msgid "lines" msgid "lines"
msgstr "" msgstr ""
#: admin.py:152 #: admin.py:152 templates/bills/microspective.html:107
msgid "total" msgid "total"
msgstr "" msgstr ""
#: admin.py:160 models.py:85 models.py:339 #: admin.py:160 models.py:85 models.py:340
msgid "type" msgid "type"
msgstr "" msgstr "tipus"
#: admin.py:177 #: admin.py:177
msgid "Payment" msgid "Payment"
msgstr "" msgstr "Pagament"
#: filters.py:17 #: filters.py:17
msgid "All" msgid "All"
msgstr "" msgstr "Tot"
#: filters.py:18 models.py:75 #: filters.py:18 models.py:75
msgid "Invoice" msgid "Invoice"
msgstr "" msgstr "Factura"
#: filters.py:19 models.py:76 #: filters.py:19 models.py:76
msgid "Amendment invoice" msgid "Amendment invoice"
msgstr "" msgstr "Factura rectificativa"
#: filters.py:20 models.py:77 #: filters.py:20 models.py:77
msgid "Fee" msgid "Fee"
msgstr "" msgstr "Quota de soci"
#: filters.py:21 #: filters.py:21
msgid "Amendment fee" msgid "Amendment fee"
msgstr "" msgstr "Rectificació de quota de soci"
#: filters.py:22 #: filters.py:22
msgid "Pro-forma" msgid "Pro-forma"
@ -157,11 +157,11 @@ msgstr ""
#: filters.py:47 #: filters.py:47
msgid "Yes" msgid "Yes"
msgstr "" msgstr "Si"
#: filters.py:48 #: filters.py:48
msgid "No" msgid "No"
msgstr "" msgstr "No"
#: forms.py:9 #: forms.py:9
msgid "Number" msgid "Number"
@ -227,7 +227,7 @@ msgstr ""
#: models.py:32 #: models.py:32
msgid "VAT number" msgid "VAT number"
msgstr "" msgstr "NIF"
#: models.py:64 #: models.py:64
msgid "Paid" msgid "Paid"
@ -285,62 +285,113 @@ msgstr ""
msgid "HTML" msgid "HTML"
msgstr "" msgstr ""
#: models.py:270 #: models.py:271
msgid "bill" msgid "bill"
msgstr "" msgstr ""
#: models.py:271 models.py:336 #: models.py:272 models.py:337 templates/bills/microspective.html:73
msgid "description" msgid "description"
msgstr "" msgstr "descripció"
#: models.py:272
msgid "rate"
msgstr ""
#: models.py:273 #: models.py:273
msgid "quantity" msgid "rate"
msgstr "" msgstr "tarifa"
#: models.py:274 #: models.py:274
msgid "quantity"
msgstr "quantitat"
#: models.py:275 templates/bills/microspective.html:76
msgid "subtotal" msgid "subtotal"
msgstr "" msgstr ""
#: models.py:275 #: models.py:276
msgid "tax" msgid "tax"
msgstr "" msgstr "impostos"
#: models.py:281 #: models.py:282
msgid "Informative link back to the order" msgid "Informative link back to the order"
msgstr "" msgstr ""
#: models.py:282 #: models.py:283
msgid "order billed" msgid "order billed"
msgstr "" msgstr ""
#: models.py:283 #: models.py:284
msgid "order billed until" msgid "order billed until"
msgstr "" msgstr ""
#: models.py:284 #: models.py:285
msgid "created" msgid "created"
msgstr "" msgstr ""
#: models.py:286 #: models.py:287
msgid "amended line" msgid "amended line"
msgstr "" msgstr ""
#: models.py:329 #: models.py:330
msgid "Volume" msgid "Volume"
msgstr "" msgstr ""
#: models.py:330 #: models.py:331
msgid "Compensation" msgid "Compensation"
msgstr "" msgstr ""
#: models.py:331 #: models.py:332
msgid "Other" msgid "Other"
msgstr "" msgstr ""
#: models.py:335 #: models.py:336
msgid "bill line" msgid "bill line"
msgstr "" msgstr ""
#: templates/bills/microspective.html:74
msgid "hrs/qty"
msgstr "hrs/quant"
#: templates/bills/microspective.html:75
msgid "rate/price"
msgstr "tarifa/preu"
#: templates/bills/microspective.html:100
#: templates/bills/microspective.html:103
msgid "VAT"
msgstr "IVA"
#: templates/bills/microspective.html:103
msgid "taxes"
msgstr ""
#: templates/bills/microspective.html:119
msgid "COMMENTS"
msgstr "COMENTARIS"
#: templates/bills/microspective.html:125
msgid "PAYMENT"
msgstr "PAGAMENT"
#: templates/bills/microspective.html:129
#, python-format
msgid ""
"\n"
" You can pay our %(type)s by bank transfer. <br>\n"
" Please make sure to state your name and the "
"%(bill.get_type_display.lower)s number.\n"
" Our bank account number is <br>\n"
" "
msgstr ""
#: templates/bills/microspective.html:138
msgid "QUESTIONS"
msgstr "PREGUNTES"
#: templates/bills/microspective.html:139
#, python-format
msgid ""
"\n"
" If you have any question about your %(type)s, please\n"
" feel free to contact us at your convinience. We will reply as "
"soon as we get\n"
" your message.\n"
" "
msgstr ""

View File

@ -3,7 +3,7 @@ from dateutil.relativedelta import relativedelta
from django.core.validators import ValidationError, RegexValidator from django.core.validators import ValidationError, RegexValidator
from django.db import models from django.db import models
from django.template import loader, Context from django.template import loader, Context
from django.utils import timezone from django.utils import timezone, translation
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -191,7 +191,7 @@ class Bill(models.Model):
self.is_sent = True self.is_sent = True
self.save(update_fields=['is_sent']) self.save(update_fields=['is_sent'])
def render(self, payment=False): def render(self, payment=False, language=None):
if payment is False: if payment is False:
payment = self.account.paymentsources.get_default() payment = self.account.paymentsources.get_default()
context = Context({ context = Context({
@ -213,7 +213,8 @@ class Bill(models.Model):
template_name = 'BILLS_%s_TEMPLATE' % self.get_type() template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE) template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE)
bill_template = loader.get_template(template) bill_template = loader.get_template(template)
html = bill_template.render(context) with translation.override(language or self.account.language):
html = bill_template.render(context)
html = html.replace('-pageskip-', '<pdf:nextpage />') html = html.replace('-pageskip-', '<pdf:nextpage />')
return html return html

View File

@ -1,66 +1,99 @@
from django.conf import settings from django.conf import settings
from django_countries import data from django_countries import data
from orchestra.settings import BASE_DOMAIN
BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4)
BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX', 'I') BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH',
4
)
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A') BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX',
'I'
)
BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX', 'F') BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX',
'A'
)
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBER_PREFIX', 'B') BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX',
'F'
)
BILLS_PROFORMA_NUMBER_PREFIX = getattr(settings, 'BILLS_PROFORMA_NUMBER_PREFIX', 'P') BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBER_PREFIX',
'B'
)
BILLS_PROFORMA_NUMBER_PREFIX = getattr(settings, 'BILLS_PROFORMA_NUMBER_PREFIX',
'P'
)
BILLS_DEFAULT_TEMPLATE = getattr(settings, 'BILLS_DEFAULT_TEMPLATE', BILLS_DEFAULT_TEMPLATE = getattr(settings, 'BILLS_DEFAULT_TEMPLATE',
'bills/microspective.html') 'bills/microspective.html'
)
BILLS_FEE_TEMPLATE = getattr(settings, 'BILLS_FEE_TEMPLATE', BILLS_FEE_TEMPLATE = getattr(settings, 'BILLS_FEE_TEMPLATE',
'bills/microspective-fee.html') 'bills/microspective-fee.html'
)
BILLS_PROFORMA_TEMPLATE = getattr(settings, 'BILLS_PROFORMA_TEMPLATE', BILLS_PROFORMA_TEMPLATE = getattr(settings, 'BILLS_PROFORMA_TEMPLATE',
'bills/microspective-proforma.html') 'bills/microspective-proforma.html'
)
BILLS_CURRENCY = getattr(settings, 'BILLS_CURRENCY', 'euro') BILLS_CURRENCY = getattr(settings, 'BILLS_CURRENCY',
'euro'
)
BILLS_SELLER_PHONE = getattr(settings, 'BILLS_SELLER_PHONE', '111-112-11-222') BILLS_SELLER_PHONE = getattr(settings, 'BILLS_SELLER_PHONE',
'111-112-11-222'
)
BILLS_SELLER_EMAIL = getattr(settings, 'BILLS_SELLER_EMAIL', 'sales@orchestra.lan') BILLS_SELLER_EMAIL = getattr(settings, 'BILLS_SELLER_EMAIL',
'sales@{}'.format(BASE_DOMAIN)
)
BILLS_SELLER_WEBSITE = getattr(settings, 'BILLS_SELLER_WEBSITE', 'www.orchestra.lan') BILLS_SELLER_WEBSITE = getattr(settings, 'BILLS_SELLER_WEBSITE',
'www.{}'.format(BASE_DOMAIN)
)
BILLS_SELLER_BANK_ACCOUNT = getattr(settings, 'BILLS_SELLER_BANK_ACCOUNT', BILLS_SELLER_BANK_ACCOUNT = getattr(settings, 'BILLS_SELLER_BANK_ACCOUNT',
'0000 0000 00 00000000 (Orchestra Bank)') '0000 0000 00 00000000 (Orchestra Bank)'
)
BILLS_EMAIL_NOTIFICATION_TEMPLATE = getattr(settings, 'BILLS_EMAIL_NOTIFICATION_TEMPLATE', BILLS_EMAIL_NOTIFICATION_TEMPLATE = getattr(settings, 'BILLS_EMAIL_NOTIFICATION_TEMPLATE',
'bills/bill-notification.email') 'bills/bill-notification.email'
)
BILLS_ORDER_MODEL = getattr(settings, 'BILLS_ORDER_MODEL', 'orders.Order') BILLS_ORDER_MODEL = getattr(settings, 'BILLS_ORDER_MODEL',
'orders.Order'
)
BILLS_CONTACT_DEFAULT_CITY = getattr(settings, 'BILLS_CONTACT_DEFAULT_CITY', 'Barcelona') BILLS_CONTACT_DEFAULT_CITY = getattr(settings, 'BILLS_CONTACT_DEFAULT_CITY',
'Barcelona'
)
BILLS_CONTACT_COUNTRIES = getattr(settings, 'BILLS_CONTACT_COUNTRIES', ((k,v) for k,v in data.COUNTRIES.iteritems())) BILLS_CONTACT_COUNTRIES = getattr(settings, 'BILLS_CONTACT_COUNTRIES',
((k,v) for k,v in data.COUNTRIES.iteritems())
)
BILLS_CONTACT_DEFAULT_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY', 'ES')
BILLS_CONTACT_DEFAULT_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY',
'ES'
)

View File

@ -123,7 +123,7 @@ div#simple table{
<table width="100%"> <table width="100%">
<tr> <tr>
<th width="5%">ID</th> <th width="5%">ID</th>
<th width="65%">Description</th> <th width="65%">{% trans Description %}</th>
<th width="20%">Amount</th> <th width="20%">Amount</th>
<th width="10%">Price</th> <th width="10%">Price</th>
</tr> </tr>

View File

@ -1,6 +1,6 @@
body { body {
/* max-width: 650px;*/ /* max-width: 650px;*/
max-width: 670px; max-width: 820px;
margin: 40 auto !important; margin: 40 auto !important;
/* margin-bottom: 30 !important;*/ /* margin-bottom: 30 !important;*/
float: none !important; float: none !important;

View File

@ -1,4 +1,5 @@
{% extends 'bills/base.html' %} {% extends 'bills/base.html' %}
{% load i18n %}
{% block head %} {% block head %}
<style type="text/css"> <style type="text/css">
@ -69,10 +70,10 @@
{% block content %} {% block content %}
<div id="lines"> <div id="lines">
<span class="title column-id">id</span> <span class="title column-id">id</span>
<span class="title column-description">description</span> <span class="title column-description">{% trans "description" %}</span>
<span class="title column-quantity">hrs/qty</span> <span class="title column-quantity">{% trans "hrs/qty" %}</span>
<span class="title column-rate">rate/price</span> <span class="title column-rate">{% trans "rate/price" %}</span>
<span class="title column-subtotal">subtotal</span> <span class="title column-subtotal">{% trans "subtotal" %}</span>
<br> <br>
{% for line in lines %} {% for line in lines %}
{% with sublines=line.sublines.all %} {% with sublines=line.sublines.all %}
@ -96,14 +97,14 @@
<div id="totals"> <div id="totals">
<br>&nbsp;<br> <br>&nbsp;<br>
{% for tax, subtotal in bill.get_subtotals.iteritems %} {% for tax, subtotal in bill.get_subtotals.iteritems %}
<span class="subtotal column-title">subtotal {{ tax }}% VAT</span> <span class="subtotal column-title">subtotal {{ tax }}% {% trans "VAT" %}</span>
<span class="subtotal column-value">{{ subtotal | first }} &{{ currency.lower }};</span> <span class="subtotal column-value">{{ subtotal | first }} &{{ currency.lower }};</span>
<br> <br>
<span class="tax column-title">taxes {{ tax }}% VAT</span> <span class="tax column-title">{% trans "taxes" %} {{ tax }}% {% trans "VAT" %}</span>
<span class="tax column-value">{{ subtotal | last }} &{{ currency.lower }};</span> <span class="tax column-value">{{ subtotal | last }} &{{ currency.lower }};</span>
<br> <br>
{% endfor %} {% endfor %}
<span class="total column-title">total</span> <span class="total column-title">{% trans "total" %}</span>
<span class="total column-value">{{ bill.get_total }} &{{ currency.lower }};</span> <span class="total column-value">{{ bill.get_total }} &{{ currency.lower }};</span>
<br> <br>
</div> </div>
@ -115,26 +116,31 @@
<div id="footer-column-1"> <div id="footer-column-1">
<div id="comments"> <div id="comments">
{% if bill.comments %} {% if bill.comments %}
<span class="title">COMMENTS</span> {{ bill.comments|linebreaksbr }} <span class="title">{% trans "COMMENTS" %}</span> {{ bill.comments|linebreaksbr }}
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div id="footer-column-2"> <div id="footer-column-2">
<div id="payment"> <div id="payment">
<span class="title">PAYMENT</span> <span class="title">{% trans "PAYMENT" %}</span>
{% if payment.message %} {% if payment.message %}
{{ payment.message | safe }} {{ payment.message | safe }}
{% else %} {% else %}
You can pay our {{ bill.get_type_display.lower }} by bank transfer. <br> {% blocktrans with type=bill.get_type_display.lower %}
You can pay our {{ type }} by bank transfer. <br>
Please make sure to state your name and the {{ bill.get_type_display.lower}} number. Please make sure to state your name and the {{ bill.get_type_display.lower}} number.
Our bank account number is <br> Our bank account number is <br>
{% endblocktrans %}
<strong>{{ seller_info.bank_account }}</strong> <strong>{{ seller_info.bank_account }}</strong>
{% endif %} {% endif %}
</div> </div>
<div id="questions"> <div id="questions">
<span class="title">QUESTIONS</span> If you have any question about your {{ bill.get_type_display.lower}}, please <span class="title">{% trans "QUESTIONS" %}</span>
{% blocktrans with type=bill.get_type_display.lower %}
If you have any question about your {{ type }}, please
feel free to contact us at your convinience. We will reply as soon as we get feel free to contact us at your convinience. We will reply as soon as we get
your message. your message.
{% endblocktrans %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,7 +12,9 @@ CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES
)) ))
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY', 'Barcelona') CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY',
'Barcelona'
)
CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', ( CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', (
@ -20,4 +22,6 @@ CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', (
)) ))
CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY', 'ES') CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY',
'ES'
)

View File

@ -7,7 +7,11 @@ DATABASES_TYPE_CHOICES = getattr(settings, 'DATABASES_TYPE_CHOICES', (
)) ))
DATABASES_DEFAULT_TYPE = getattr(settings, 'DATABASES_DEFAULT_TYPE', 'mysql') DATABASES_DEFAULT_TYPE = getattr(settings, 'DATABASES_DEFAULT_TYPE',
'mysql'
)
DATABASES_DEFAULT_HOST = getattr(settings, 'DATABASES_DEFAULT_HOST', 'localhost') DATABASES_DEFAULT_HOST = getattr(settings, 'DATABASES_DEFAULT_HOST',
'localhost'
)

View File

@ -1,57 +1,83 @@
from django.conf import settings from django.conf import settings
from orchestra.settings import BASE_DOMAIN
DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER', DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER',
'ns.orchestra.lan') 'ns.{}'.format(BASE_DOMAIN)
)
DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER', DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER',
'hostmaster@orchestra.lan') 'hostmaster@{}'.format(BASE_DOMAIN)
)
DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL', '1h') DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL',
'1h'
)
DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH', '1d') DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH',
'1d'
)
DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY', '2h') DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY',
'2h'
)
DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION', '4w') DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION',
'4w'
)
DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME', '1h') DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME',
'1h'
)
DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH', '/etc/bind/master/%(name)s') DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH',
'/etc/bind/master/%(name)s'
)
DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH', '/etc/bind/named.conf.local') DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH',
'/etc/bind/named.conf.local'
)
DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.conf.local') DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH',
'/etc/bind/named.conf.local'
)
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH', DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
'/usr/sbin/named-checkzone -i local -k fail -n fail') '/usr/sbin/named-checkzone -i local -k fail -n fail'
)
DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm') # Used for creating temporary zone files used for validation
DOMAINS_ZONE_VALIDATION_TMP_DIR = getattr(settings, 'DOMAINS_ZONE_VALIDATION_TMP_DIR',
'/dev/shm'
)
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13') DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A',
'10.0.3.13'
)
DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', ( DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', (
'10 mail.orchestra.lan.', '10 mail.{}.'.format(BASE_DOMAIN),
'10 mail2.orchestra.lan.', '10 mail2.{}.'.format(BASE_DOMAIN),
)) ))
DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', ( DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', (
'ns1.orchestra.lan.', 'ns1.{}.'.format(BASE_DOMAIN),
'ns2.orchestra.lan.', 'ns2.{}.'.format(BASE_DOMAIN),
)) ))
@ -61,5 +87,5 @@ DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN',
# wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip # wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip
# unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list # unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list
# '%(site_root)s/forbidden_domains.list') # '%(site_dir)s/forbidden_domains.list')
'') '')

View File

@ -13,7 +13,7 @@ from . import settings
def validate_allowed_domain(value): def validate_allowed_domain(value):
context = { context = {
'site_root': paths.get_site_root() 'site_dir': paths.get_site_dir()
} }
fname = settings.DOMAINS_FORBIDDEN fname = settings.DOMAINS_FORBIDDEN
if fname: if fname:
@ -108,12 +108,15 @@ def validate_soa_record(value):
def validate_zone(zone): def validate_zone(zone):
""" Ultimate zone file validation using named-checkzone """ """ Ultimate zone file validation using named-checkzone """
zone_name = zone.split()[0][:-1] zone_name = zone.split()[0][:-1]
path = os.path.join(settings.DOMAINS_CHECKZONE_PATH, zone_name) zone_path = os.path.join(settings.DOMAINS_ZONE_VALIDATION_TMP_DIR, zone_name)
with open(path, 'wb') as f:
f.write(zone)
# Don't use /dev/stdin becuase the 'argument list is too long' error
checkzone = settings.DOMAINS_CHECKZONE_BIN_PATH checkzone = settings.DOMAINS_CHECKZONE_BIN_PATH
check = run(' '.join([checkzone, zone_name, path]), error_codes=[0,1], display=False) try:
with open(zone_path, 'wb') as f:
f.write(zone_path)
# Don't use /dev/stdin becuase the 'argument list is too long' error
check = run(' '.join([checkzone, zone_name, zone_path]), error_codes=[0,1], display=False)
finally:
os.unlink(zone_path)
if check.return_code == 1: if check.return_code == 1:
errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1] errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1]
raise ValidationError(', '.join(errors)) raise ValidationError(', '.join(errors))

View File

@ -1,7 +1,10 @@
from django.conf import settings from django.conf import settings
ISSUES_SUPPORT_EMAILS = getattr(settings, 'ISSUES_SUPPORT_EMAILS', []) ISSUES_SUPPORT_EMAILS = getattr(settings, 'ISSUES_SUPPORT_EMAILS', [
])
ISSUES_NOTIFY_SUPERUSERS = getattr(settings, 'ISSUES_NOTIFY_SUPERUSERS', True) ISSUES_NOTIFY_SUPERUSERS = getattr(settings, 'ISSUES_NOTIFY_SUPERUSERS',
True
)

View File

@ -1,24 +1,38 @@
from django.conf import settings from django.conf import settings
from orchestra.settings import BASE_DOMAIN
LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain')
LISTS_DEFAULT_DOMAIN = getattr(settings, 'LIST_DEFAULT_DOMAIN', 'lists.orchestra.lan') LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL',
'domains.Domain'
)
LISTS_LIST_URL = getattr(settings, 'LISTS_LIST_URL', 'https://lists.orchestra.lan/mailman/listinfo/%(name)s') LISTS_DEFAULT_DOMAIN = getattr(settings, 'LIST_DEFAULT_DOMAIN',
'lists.{}'.format(BASE_DOMAIN)
)
LISTS_LIST_URL = getattr(settings, 'LISTS_LIST_URL',
'https://lists.{}/mailman/listinfo/%(name)s'.format(BASE_DOMAIN)
)
LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH', LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH',
'/var/log/mailman/post') '/var/log/mailman/post'
)
LISTS_MAILMAN_ROOT_PATH = getattr(settings, 'LISTS_MAILMAN_ROOT_PATH', LISTS_MAILMAN_ROOT_PATH = getattr(settings, 'LISTS_MAILMAN_ROOT_PATH',
'/var/lib/mailman/') '/var/lib/mailman/'
)
LISTS_VIRTUAL_ALIAS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_PATH', LISTS_VIRTUAL_ALIAS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_PATH',
'/etc/postfix/mailman_virtual_aliases') '/etc/postfix/mailman_virtual_aliases'
)
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_DOMAINS_PATH', LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_DOMAINS_PATH',
'/etc/postfix/mailman_virtual_domains') '/etc/postfix/mailman_virtual_domains'
)

View File

@ -4,42 +4,57 @@ import textwrap
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.settings import BASE_DOMAIN
MAILBOXES_DOMAIN_MODEL = getattr(settings, 'MAILBOXES_DOMAIN_MODEL', 'domains.Domain')
MAILBOXES_HOME = getattr(settings, 'MAILBOXES_HOME', '/home/./%(name)s/') MAILBOXES_DOMAIN_MODEL = getattr(settings, 'MAILBOXES_DOMAIN_MODEL',
'domains.Domain'
)
MAILBOXES_HOME = getattr(settings, 'MAILBOXES_HOME',
'/home/%(name)s/'
)
MAILBOXES_SIEVE_PATH = getattr(settings, 'MAILBOXES_SIEVE_PATH', MAILBOXES_SIEVE_PATH = getattr(settings, 'MAILBOXES_SIEVE_PATH',
os.path.join(MAILBOXES_HOME, 'Maildir/sieve/orchestra.sieve')) os.path.join(MAILBOXES_HOME, 'Maildir/sieve/orchestra.sieve')
)
MAILBOXES_SIEVETEST_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_PATH', '/dev/shm') MAILBOXES_SIEVETEST_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_PATH',
'/dev/shm'
)
MAILBOXES_SIEVETEST_BIN_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_BIN_PATH', MAILBOXES_SIEVETEST_BIN_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_BIN_PATH',
'%(orchestra_root)s/bin/sieve-test') '%(orchestra_root)s/bin/sieve-test'
)
MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH', MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH',
'/etc/postfix/virtual_mailboxes') '/etc/postfix/virtual_mailboxes'
)
MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH', MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH',
'/etc/postfix/virtual_aliases') '/etc/postfix/virtual_aliases'
)
MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH', MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH',
'/etc/postfix/virtual_domains') '/etc/postfix/virtual_domains'
)
MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN = getattr(settings, 'MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN', MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN = getattr(settings, 'MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN',
'orchestra.lan') BASE_DOMAIN
)
MAILBOXES_PASSWD_PATH = getattr(settings, 'MAILBOXES_PASSWD_PATH', MAILBOXES_PASSWD_PATH = getattr(settings, 'MAILBOXES_PASSWD_PATH',
'/etc/dovecot/passwd') '/etc/dovecot/passwd'
)
MAILBOXES_MAILBOX_FILTERINGS = getattr(settings, 'MAILBOXES_MAILBOX_FILTERINGS', { MAILBOXES_MAILBOX_FILTERINGS = getattr(settings, 'MAILBOXES_MAILBOX_FILTERINGS', {
@ -62,11 +77,15 @@ MAILBOXES_MAILBOX_FILTERINGS = getattr(settings, 'MAILBOXES_MAILBOX_FILTERINGS',
MAILBOXES_MAILBOX_DEFAULT_FILTERING = getattr(settings, 'MAILBOXES_MAILBOX_DEFAULT_FILTERING', MAILBOXES_MAILBOX_DEFAULT_FILTERING = getattr(settings, 'MAILBOXES_MAILBOX_DEFAULT_FILTERING',
'REDIRECT') 'REDIRECT'
)
MAILBOXES_MAILDIRSIZE_PATH = getattr(settings, 'MAILBOXES_MAILDIRSIZE_PATH', '%(home)s/Maildir/maildirsize') MAILBOXES_MAILDIRSIZE_PATH = getattr(settings, 'MAILBOXES_MAILDIRSIZE_PATH',
'%(home)s/Maildir/maildirsize'
)
MAILBOXES_LOCAL_ADDRESS_DOMAIN = getattr(settings, 'MAILBOXES_LOCAL_ADDRESS_DOMAIN', MAILBOXES_LOCAL_ADDRESS_DOMAIN = getattr(settings, 'MAILBOXES_LOCAL_ADDRESS_DOMAIN',
'orchestra.lan') BASE_DOMAIN
)

View File

@ -55,7 +55,7 @@ def validate_sieve(value):
with open(path, 'wb') as f: with open(path, 'wb') as f:
f.write(value) f.write(value)
context = { context = {
'orchestra_root': paths.get_orchestra_root() 'orchestra_root': paths.get_orchestra_dir()
} }
sievetest = settings.MAILBOXES_SIEVETEST_BIN_PATH % context sievetest = settings.MAILBOXES_SIEVETEST_BIN_PATH % context
try: try:

View File

@ -72,7 +72,7 @@ class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelA
def get_service(self, obj): def get_service(self, obj):
if obj is None: if obj is None:
return self.plugin.get_plugin(self.plugin_value).related_instance return self.plugin.get(self.plugin_value).related_instance
else: else:
return obj.service return obj.service

View File

@ -1,5 +1,6 @@
from django.conf import settings from django.conf import settings
MISCELLANEOUS_IDENTIFIER_VALIDATORS = getattr(settings, 'MISCELLANEOUS_IDENTIFIER_VALIDATORS', {}) MISCELLANEOUS_IDENTIFIER_VALIDATORS = getattr(settings, 'MISCELLANEOUS_IDENTIFIER_VALIDATORS', {
# MISCELLANEOUS_IDENTIFIER_VALIDATORS = { miscservice__name: validator_function } # <miscservice__name>: <validator_function>
})

View File

@ -32,7 +32,7 @@ class RouteAdmin(admin.ModelAdmin):
BACKEND_HELP_TEXT = { BACKEND_HELP_TEXT = {
backend: "This backend operates over '%s'" % ServiceBackend.get_backend(backend).model backend: "This backend operates over '%s'" % ServiceBackend.get_backend(backend).model
for backend, __ in ServiceBackend.get_plugin_choices() for backend, __ in ServiceBackend.get_choices()
} }
DEFAULT_MATCH = { DEFAULT_MATCH = {
backend.get_name(): backend.default_route_match for backend in ServiceBackend.get_backends(active=False) backend.get_name(): backend.default_route_match for backend in ServiceBackend.get_backends(active=False)

View File

@ -119,7 +119,7 @@ class ServiceBackend(plugins.Plugin):
@classmethod @classmethod
def get_backend(cls, name): def get_backend(cls, name):
return cls.get_plugin(name) return cls.get(name)
@classmethod @classmethod
def model_class(cls): def model_class(cls):

View File

@ -170,7 +170,7 @@ class Route(models.Model):
Defines the routing that determine in which server a backend is executed Defines the routing that determine in which server a backend is executed
""" """
backend = models.CharField(_("backend"), max_length=256, backend = models.CharField(_("backend"), max_length=256,
choices=ServiceBackend.get_plugin_choices()) choices=ServiceBackend.get_choices())
host = models.ForeignKey(Server, verbose_name=_("host")) host = models.ForeignKey(Server, verbose_name=_("host"))
match = models.CharField(_("match"), max_length=256, blank=True, default='True', match = models.CharField(_("match"), max_length=256, blank=True, default='True',
help_text=_("Python expression used for selecting the targe host, " help_text=_("Python expression used for selecting the targe host, "

View File

@ -24,7 +24,7 @@ class RouterTests(BaseTestCase):
def save(self, instance): def save(self, instance):
pass pass
choices = backends.ServiceBackend.get_plugin_choices() choices = backends.ServiceBackend.get_choices()
Route._meta.get_field_by_name('backend')[0]._choices = choices Route._meta.get_field_by_name('backend')[0]._choices = choices
backend = TestBackend.get_name() backend = TestBackend.get_name()

View File

@ -64,7 +64,7 @@ class BillsBackend(object):
if service.metric and service.billing_period != service.NEVER and service.pricing_period == service.NEVER: if service.metric and service.billing_period != service.NEVER and service.pricing_period == service.NEVER:
metric = format(line.metric, '.2f').rstrip('0').rstrip('.') metric = format(line.metric, '.2f').rstrip('0').rstrip('.')
size = format(line.size, '.2f').rstrip('0').rstrip('.') size = format(line.size, '.2f').rstrip('0').rstrip('.')
description += " (%s*%s)" % (metric, size) description += " (%sx%s)" % (metric, size)
return description return description
def create_sublines(self, line, discounts): def create_sublines(self, line, discounts):

View File

@ -237,6 +237,7 @@ class Order(models.Model):
class MetricStorage(models.Model): class MetricStorage(models.Model):
""" Stores metric state for future billing """
order = models.ForeignKey(Order, verbose_name=_("order"), related_name='metrics') order = models.ForeignKey(Order, verbose_name=_("order"), related_name='metrics')
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2) value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
created_on = models.DateField(_("created"), auto_now_add=True) created_on = models.DateField(_("created"), auto_now_add=True)
@ -253,16 +254,16 @@ class MetricStorage(models.Model):
def store(cls, order, value): def store(cls, order, value):
now = timezone.now() now = timezone.now()
try: try:
metric = cls.objects.filter(order=order).latest() last = cls.objects.filter(order=order).latest()
except cls.DoesNotExist: except cls.DoesNotExist:
cls.objects.create(order=order, value=value, updated_on=now) cls.objects.create(order=order, value=value, updated_on=now)
else: else:
threshold = decimal.Decimal(settings.ORDERS_METRIC_THRESHOLD) error = decimal.Decimal(settings.ORDERS_METRIC_ERROR)
if metric.value*(1+threshold) > value or metric.value*threshold < value: if last.value*(1+error) > value or last.value*error < value:
cls.objects.create(order=order, value=value, updated_on=now) cls.objects.create(order=order, value=value, updated_on=now)
else: else:
metric.updated_on = now last.updated_on = now
metric.save(update_fields=['updated_on']) last.save(update_fields=['updated_on'])
accounts.register(Order) accounts.register(Order)

View File

@ -3,11 +3,14 @@ from django.conf import settings
# Pluggable backend for bill generation. # Pluggable backend for bill generation.
ORDERS_BILLING_BACKEND = getattr(settings, 'ORDERS_BILLING_BACKEND', ORDERS_BILLING_BACKEND = getattr(settings, 'ORDERS_BILLING_BACKEND',
'orchestra.apps.orders.billing.BillsBackend') 'orchestra.apps.orders.billing.BillsBackend'
)
# Pluggable service class # Pluggable service class
ORDERS_SERVICE_MODEL = getattr(settings, 'ORDERS_SERVICE_MODEL', 'services.Service') ORDERS_SERVICE_MODEL = getattr(settings, 'ORDERS_SERVICE_MODEL',
'services.Service'
)
# Prevent inspecting these apps for service accounting # Prevent inspecting these apps for service accounting
@ -26,4 +29,6 @@ ORDERS_EXCLUDED_APPS = getattr(settings, 'ORDERS_EXCLUDED_APPS', (
# Only account for significative changes # Only account for significative changes
# metric_storage new value: lastvalue*(1+threshold) > currentvalue or lastvalue*threshold < currentvalue # metric_storage new value: lastvalue*(1+threshold) > currentvalue or lastvalue*threshold < currentvalue
ORDERS_METRIC_THRESHOLD = getattr(settings, 'ORDERS_METRIC_THRESHOLD', 0.4) ORDERS_METRIC_ERROR = getattr(settings, 'ORDERS_METRIC_ERROR',
0.01
)

View File

@ -23,7 +23,7 @@ def process_transactions(modeladmin, request, queryset):
return return
for method, transactions in queryset.group_by('source__method').iteritems(): for method, transactions in queryset.group_by('source__method').iteritems():
if method is not None: if method is not None:
method = PaymentMethod.get_plugin(method) method = PaymentMethod.get(method)
procs = method.process(transactions) procs = method.process(transactions)
processes += procs processes += procs
for trans in transactions: for trans in transactions:

View File

@ -20,7 +20,7 @@ class PaymentSource(models.Model):
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='paymentsources') related_name='paymentsources')
method = models.CharField(_("method"), max_length=32, method = models.CharField(_("method"), max_length=32,
choices=PaymentMethod.get_plugin_choices()) choices=PaymentMethod.get_choices())
data = JSONField(_("data"), default={}) data = JSONField(_("data"), default={})
is_active = models.BooleanField(_("active"), default=True) is_active = models.BooleanField(_("active"), default=True)
@ -31,7 +31,7 @@ class PaymentSource(models.Model):
@cached_property @cached_property
def method_class(self): def method_class(self):
return PaymentMethod.get_plugin(self.method) return PaymentMethod.get(self.method)
@cached_property @cached_property
def method_instance(self): def method_instance(self):

View File

@ -12,7 +12,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
fields = ('url', 'method', 'data', 'is_active') fields = ('url', 'method', 'data', 'is_active')
def validate_data(self, attrs, source): def validate_data(self, attrs, source):
plugin = PaymentMethod.get_plugin(attrs['method']) plugin = PaymentMethod.get(attrs['method'])
serializer_class = plugin().get_serializer() serializer_class = plugin().get_serializer()
serializer = serializer_class(data=attrs[source]) serializer = serializer_class(data=attrs[source])
if not serializer.is_valid(): if not serializer.is_valid():
@ -23,7 +23,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
if not obj: if not obj:
return {} return {}
if obj.method: if obj.method:
plugin = PaymentMethod.get_plugin(obj.method) plugin = PaymentMethod.get(obj.method)
serializer_class = plugin().get_serializer() serializer_class = plugin().get_serializer()
return serializer_class().to_native(obj.data) return serializer_class().to_native(obj.data)
return obj.data return obj.data

View File

@ -1,18 +1,23 @@
from django.conf import settings from django.conf import settings
PAYMENT_CURRENCY = getattr(settings, 'PAYMENT_CURRENCY', 'Eur') PAYMENT_CURRENCY = getattr(settings, 'PAYMENT_CURRENCY',
'Eur'
)
PAYMENTS_DD_CREDITOR_NAME = getattr(settings, 'PAYMENTS_DD_CREDITOR_NAME', PAYMENTS_DD_CREDITOR_NAME = getattr(settings, 'PAYMENTS_DD_CREDITOR_NAME',
'Orchestra') 'Orchestra')
PAYMENTS_DD_CREDITOR_IBAN = getattr(settings, 'PAYMENTS_DD_CREDITOR_IBAN', PAYMENTS_DD_CREDITOR_IBAN = getattr(settings, 'PAYMENTS_DD_CREDITOR_IBAN',
'IE98BOFI90393912121212') 'IE98BOFI90393912121212')
PAYMENTS_DD_CREDITOR_BIC = getattr(settings, 'PAYMENTS_DD_CREDITOR_BIC', PAYMENTS_DD_CREDITOR_BIC = getattr(settings, 'PAYMENTS_DD_CREDITOR_BIC',
'BOFIIE2D') 'BOFIIE2D')
PAYMENTS_DD_CREDITOR_AT02_ID = getattr(settings, 'PAYMENTS_DD_CREDITOR_AT02_ID', PAYMENTS_DD_CREDITOR_AT02_ID = getattr(settings, 'PAYMENTS_DD_CREDITOR_AT02_ID',
'InvalidAT02ID') 'InvalidAT02ID')

View File

@ -84,8 +84,15 @@ class Rate(models.Model):
return "{}-{}".format(str(self.price), self.quantity) return "{}-{}".format(str(self.price), self.quantity)
@classmethod @classmethod
def get_methods(self): def get_methods(cls):
return self.RATE_METHODS return cls.RATE_METHODS
@classmethod
def get_choices(cls):
choices = []
for name, method in cls.RATE_METHODS.iteritems():
choices.append((name, method.verbose_name))
return choices
accounts.register(ContractedPlan) accounts.register(ContractedPlan)

View File

@ -1,5 +1,7 @@
import sys import sys
from django.utils.translation import string_concat, ugettext_lazy as _
from orchestra.utils.python import AttrDict from orchestra.utils.python import AttrDict
@ -117,6 +119,8 @@ def step_price(rates, metric):
ix += 1 ix += 1
minimal = min(minimal, (value, result), key=lambda v: v[0]) minimal = min(minimal, (value, result), key=lambda v: v[0])
return minimal[1] return minimal[1]
step_price.verbose_name = _("Step price")
step_price.help_text = _("All price rates with a lower metric are applied.")
def match_price(rates, metric): def match_price(rates, metric):
@ -144,3 +148,5 @@ def match_price(rates, metric):
'price': candidates[0].price, 'price': candidates[0].price,
})] })]
return None return None
match_price.verbose_name = _("Match price")
match_price.help_text = _("Only the rate with inmediate inferior metric is applied.")

View File

@ -1,34 +0,0 @@
import datetime
def compute_resource_usage(data):
""" Computes MonitorData.used based on related monitors """
resource = data.resource
result = 0
has_result = False
today = datetime.date.today()
for dataset in data.get_monitor_datasets():
if resource.period == resource.MONTHLY_AVG:
last = dataset.latest()
epoch = datetime(
year=today.year,
month=today.month,
day=1,
tzinfo=timezone.utc
)
total = (last.created_at-epoch).total_seconds()
ini = epoch
for data in dataset:
slot = (data.created_at-ini).total_seconds()
result += data.value * slot/total
ini = data.created_at
elif resource.period in (resource.MONTHLY_SUM, resource.LAST):
# FIXME Aggregation of 0s returns None! django bug?
# value = dataset.aggregate(models.Sum('value'))['value__sum']
values = dataset.values_list('value', flat=True)
if values:
has_result = True
result += sum(values)
else:
raise NotImplementedError("%s support not implemented" % data.period)
return float(result)/resource.get_scale() if has_result else None

View File

@ -0,0 +1,89 @@
import datetime
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra import plugins
class DataMethod(plugins.Plugin):
""" filters and computes dataset usage """
__metaclass__ = plugins.PluginMount
def filter(self, dataset):
""" Filter the dataset to get the relevant data according to the period """
raise NotImplementedError
def compute_usage(self, dataset):
""" given a dataset computes its usage according to the method (avg, sum, ...) """
raise NotImplementedError
class Last(DataMethod):
name = 'last'
verbose_name = _("Last")
def filter(self, dataset):
try:
return dataset.order_by('object_id', '-id').distinct('object_id')
except MonitorData.DoesNotExist:
return dataset.none()
def compute_usage(self, dataset):
# FIXME Aggregation of 0s returns None! django bug?
# value = dataset.aggregate(models.Sum('value'))['value__sum']
values = dataset.values_list('value', flat=True)
if values:
return sum(values)
return None
class MonthlySum(Last):
name = 'monthly-sum'
verbose_name = _("Monthly Sum")
def filter(self, dataset):
today = timezone.now()
return dataset.filter(
created_at__year=today.year,
created_at__month=today.month
)
class MonthlyAvg(MonthlySum):
name = 'monthly-avg'
verbose_name = _("Monthly AVG")
def get_epoch(self):
today = timezone.now()
return datetime(
year=today.year,
month=today.month,
day=1,
tzinfo=timezone.utc
)
def compute_usage(self, dataset):
last = dataset.latest()
epoch = self.get_epoch()
total = (last.created_at-epoch).total_seconds()
ini = epoch
result = 0
for data in dataset:
slot = (data.created_at-ini).total_seconds()
result += data.value * slot/total
ini = data.created_at
return result
class Last10DaysAvg(MonthlyAvg):
name = 'last-10-days-avg'
verbose_name = _("Last 10 days AVG")
days = 10
def get_epoch(self):
today = timezone.now()
return today - datetime.timedelta(days=self.days)
def filter(self, dataset):
return dataset.filter(created_at__gt=self.get_epoch())

View File

@ -11,11 +11,12 @@ from djcelery.models import PeriodicTask, CrontabSchedule
from orchestra.core import validators from orchestra.core import validators
from orchestra.models import queryset, fields from orchestra.models import queryset, fields
from orchestra.models.utils import get_model_field_path from orchestra.models.utils import get_model_field_path
from orchestra.utils.paths import get_project_root from orchestra.utils.paths import get_project_dir
from orchestra.utils.system import run from orchestra.utils.system import run
from . import helpers, tasks from . import tasks
from .backends import ServiceMonitor from .backends import ServiceMonitor
from .methods import DataMethod
from .validators import validate_scale from .validators import validate_scale
@ -34,8 +35,8 @@ class Resource(models.Model):
MONTHLY_AVG = 'MONTHLY_AVG' MONTHLY_AVG = 'MONTHLY_AVG'
PERIODS = ( PERIODS = (
(LAST, _("Last")), (LAST, _("Last")),
(MONTHLY_SUM, _("Monthly Sum")), (MONTHLY_SUM, _("Monthly sum")),
(MONTHLY_AVG, _("Monthly Average")), (MONTHLY_AVG, _("Monthly avg")),
) )
_related = set() # keeps track of related models for resource cleanup _related = set() # keeps track of related models for resource cleanup
@ -46,9 +47,10 @@ class Resource(models.Model):
verbose_name = models.CharField(_("verbose name"), max_length=256) verbose_name = models.CharField(_("verbose name"), max_length=256)
content_type = models.ForeignKey(ContentType, content_type = models.ForeignKey(ContentType,
help_text=_("Model where this resource will be hooked.")) help_text=_("Model where this resource will be hooked."))
period = models.CharField(_("period"), max_length=16, choices=PERIODS, # TODO rename to aggregation
default=LAST, period = models.CharField(_("aggregation"), max_length=16,
help_text=_("Operation used for aggregating this resource monitored data.")) choices=DataMethod.get_choices(), default=DataMethod.get_choices()[0][0],
help_text=_("Method used for aggregating this resource monitored data."))
on_demand = models.BooleanField(_("on demand"), default=False, on_demand = models.BooleanField(_("on demand"), default=False,
help_text=_("If enabled the resource will not be pre-allocated, " help_text=_("If enabled the resource will not be pre-allocated, "
"but allocated under the application demand")) "but allocated under the application demand"))
@ -69,7 +71,7 @@ class Resource(models.Model):
help_text=_("Crontab for periodic execution. " help_text=_("Crontab for periodic execution. "
"Leave it empty to disable periodic monitoring")) "Leave it empty to disable periodic monitoring"))
monitors = fields.MultiSelectField(_("monitors"), max_length=256, blank=True, monitors = fields.MultiSelectField(_("monitors"), max_length=256, blank=True,
choices=ServiceMonitor.get_plugin_choices(), choices=ServiceMonitor.get_choices(),
help_text=_("Monitor backends used for monitoring this resource.")) help_text=_("Monitor backends used for monitoring this resource."))
is_active = models.BooleanField(_("active"), default=True) is_active = models.BooleanField(_("active"), default=True)
@ -84,6 +86,15 @@ class Resource(models.Model):
def __unicode__(self): def __unicode__(self):
return "{}-{}".format(str(self.content_type), self.name) return "{}-{}".format(str(self.content_type), self.name)
@cached_property
def method_class(self):
return DataMethod.get(self.period)
@cached_property
def method_instance(self):
""" Per request lived type_instance """
return self.method_class(self)
def clean(self): def clean(self):
self.verbose_name = self.verbose_name.strip() self.verbose_name = self.verbose_name.strip()
if self.on_demand and self.default_allocation: if self.on_demand and self.default_allocation:
@ -114,7 +125,7 @@ class Resource(models.Model):
self.sync_periodic_task() self.sync_periodic_task()
# This only work on tests (multiprocessing used on real deployments) # This only work on tests (multiprocessing used on real deployments)
apps.get_app_config('resources').reload_relations() apps.get_app_config('resources').reload_relations()
run('sleep 2 && touch %s/wsgi.py' % get_project_root(), async=True) run('sleep 2 && touch %s/wsgi.py' % get_project_dir(), async=True)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
super(Resource, self).delete(*args, **kwargs) super(Resource, self).delete(*args, **kwargs)
@ -201,7 +212,16 @@ class ResourceData(models.Model):
return self.resource.unit return self.resource.unit
def get_used(self): def get_used(self):
return helpers.compute_resource_usage(self) resource = data.resource
total = 0
has_result = False
today = datetime.date.today()
for dataset in data.get_monitor_datasets():
usage = data.method_instance.compute_usage(dataset)
if usage is not None:
has_result = True
total += usage
return float(total)/resource.get_scale() if has_result else None
def update(self, current=None): def update(self, current=None):
if current is None: if current is None:
@ -218,10 +238,9 @@ class ResourceData(models.Model):
def get_monitor_datasets(self): def get_monitor_datasets(self):
resource = self.resource resource = self.resource
today = timezone.now()
datasets = [] datasets = []
for monitor in resource.monitors: for monitor in resource.monitors:
path = self.resource.get_model_path(monitor) path = resource.get_model_path(monitor)
if path == []: if path == []:
dataset = MonitorData.objects.filter( dataset = MonitorData.objects.filter(
monitor=monitor, monitor=monitor,
@ -239,30 +258,16 @@ class ResourceData(models.Model):
content_type=ct, content_type=ct,
object_id__in=pks object_id__in=pks
) )
if resource.period in (resource.MONTHLY_AVG, resource.MONTHLY_SUM): datasets.append(
datasets.append( resource.method_instance.filter(dataset)
dataset.filter( )
created_at__year=today.year,
created_at__month=today.month
)
)
elif resource.period == resource.LAST:
# Get last monitoring data per object_id
try:
datasets.append(
dataset.order_by('object_id', '-id').distinct('object_id')
)
except MonitorData.DoesNotExist:
continue
else:
raise NotImplementedError("%s support not implemented" % self.period)
return datasets return datasets
class MonitorData(models.Model): class MonitorData(models.Model):
""" Stores monitored data """ """ Stores monitored data """
monitor = models.CharField(_("monitor"), max_length=256, monitor = models.CharField(_("monitor"), max_length=256,
choices=ServiceMonitor.get_plugin_choices()) choices=ServiceMonitor.get_choices())
content_type = models.ForeignKey(ContentType, verbose_name=_("content type")) content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
object_id = models.PositiveIntegerField(_("object id")) object_id = models.PositiveIntegerField(_("object id"))
created_at = models.DateTimeField(_("created"), default=timezone.now) created_at = models.DateTimeField(_("created"), default=timezone.now)

View File

@ -0,0 +1,52 @@
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
from .. import settings
class BSCWBackend(ServiceController):
verbose_name = _("BSCW SaaS")
model = 'saas.SaaS'
default_route_match = "saas.service == 'bscw'"
actions = ('save', 'delete', 'validate_creation')
def validate_creation(self, saas):
context = self.get_context(saas)
self.append(textwrap.dedent("""\
if [[ $(%(bsadmin)s register %(email)s) ]]; then
echo 'ValidationError: email-exists'
elif [[ $(%(bsadmin)s users -n %(username)s) ]]; then
echo 'ValidationError: username-exists'
fi""") % context
)
def save(self, saas):
context = self.get_context(saas)
if hasattr(saas, 'password'):
self.append(textwrap.dedent("""\
if [[ ! $(%(bsadmin)s register %(email)s) && ! $(%(bsadmin)s users -n %(username)s) ]]; then
# Change password
%(bsadmin)s chpwd %(username)s '%(password)s'
else
# Create new user
%(bsadmin)s register -r %(email)s %(username)s '%(password)s'
fi
""") % context
)
elif saas.active:
self.append("%(bsadmin)s chpwd -u %(username)s" % context)
else:
self.append("%(bsadmin)s chpwd -l %(username)s" % context)
def delete(self, saas):
context = self.get_context(saas)
self.append("%(bsadmin)s rmuser -n %(username)s" % context)
def get_context(self, saas):
return {
'bsadmin': settings.SAAS_BSCW_BSADMIN_PATH,
'email': saas.data.get('email'),
'username': saas.name,
'password': getattr(saas, 'password', None),
}

View File

@ -95,9 +95,9 @@ class GitLabSaaSBackend(ServiceController):
users = json.loads(requests.get(users_url, headers=self.headers).content) users = json.loads(requests.get(users_url, headers=self.headers).content)
for user in users: for user in users:
if user['username'] == username: if user['username'] == username:
print 'user-exists' print 'ValidationError: user-exists'
if user['email'] == email: if user['email'] == email:
print 'email-exists' print 'ValidationError: email-exists'
def validate_creation(self, saas): def validate_creation(self, saas):
self.append(self._validate_creation, saas) self.append(self._validate_creation, saas)

View File

@ -14,7 +14,7 @@ from .services import SoftwareService
class SaaS(models.Model): class SaaS(models.Model):
service = models.CharField(_("service"), max_length=32, service = models.CharField(_("service"), max_length=32,
choices=SoftwareService.get_plugin_choices()) choices=SoftwareService.get_choices())
name = models.CharField(_("Name"), max_length=64, name = models.CharField(_("Name"), max_length=64,
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])
@ -41,7 +41,7 @@ class SaaS(models.Model):
@cached_property @cached_property
def service_class(self): def service_class(self):
return SoftwareService.get_plugin(self.service) return SoftwareService.get(self.service)
@cached_property @cached_property
def service_instance(self): def service_instance(self):

View File

@ -1,5 +1,7 @@
from django.conf import settings from django.conf import settings
from orchestra.settings import BASE_DOMAIN
SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', ( SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
'orchestra.apps.saas.services.moodle.MoodleService', 'orchestra.apps.saas.services.moodle.MoodleService',
@ -17,18 +19,22 @@ SAAS_WORDPRESS_ADMIN_PASSWORD = getattr(settings, 'SAAS_WORDPRESSMU_ADMIN_PASSWO
'secret' 'secret'
) )
SAAS_WORDPRESS_BASE_URL = getattr(settings, 'SAAS_WORDPRESS_BASE_URL', SAAS_WORDPRESS_BASE_URL = getattr(settings, 'SAAS_WORDPRESS_BASE_URL',
'http://blogs.orchestra.lan/' 'http://blogs.{}/'.format(BASE_DOMAIN)
) )
SAAS_DOKUWIKI_TEMPLATE_PATH = getattr(settings, 'SAAS_DOKUWIKI_TEMPLATE_PATH', SAAS_DOKUWIKI_TEMPLATE_PATH = getattr(settings, 'SAAS_DOKUWIKI_TEMPLATE_PATH',
'/home/httpd/htdocs/wikifarm/template.tar.gz') '/home/httpd/htdocs/wikifarm/template.tar.gz'
)
SAAS_DOKUWIKI_FARM_PATH = getattr(settings, 'WEBSITES_DOKUWIKI_FARM_PATH', SAAS_DOKUWIKI_FARM_PATH = getattr(settings, 'WEBSITES_DOKUWIKI_FARM_PATH',
'/home/httpd/htdocs/wikifarm/farm' '/home/httpd/htdocs/wikifarm/farm'
) )
SAAS_DRUPAL_SITES_PATH = getattr(settings, 'WEBSITES_DRUPAL_SITES_PATH', SAAS_DRUPAL_SITES_PATH = getattr(settings, 'WEBSITES_DRUPAL_SITES_PATH',
'/home/httpd/htdocs/drupal-mu/sites/%(site_name)s' '/home/httpd/htdocs/drupal-mu/sites/%(site_name)s'
) )
@ -38,34 +44,42 @@ SAAS_PHPLIST_DB_NAME = getattr(settings, 'SAAS_PHPLIST_DB_NAME',
'phplist_mu' 'phplist_mu'
) )
SAAS_PHPLIST_BASE_DOMAIN = getattr(settings, 'SAAS_PHPLIST_BASE_DOMAIN', SAAS_PHPLIST_BASE_DOMAIN = getattr(settings, 'SAAS_PHPLIST_BASE_DOMAIN',
'lists.orchestra.lan' 'lists.{}'.format(BASE_DOMAIN)
) )
SAAS_SEAFILE_DOMAIN = getattr(settings, 'SAAS_SEAFILE_DOMAIN', SAAS_SEAFILE_DOMAIN = getattr(settings, 'SAAS_SEAFILE_DOMAIN',
'seafile.orchestra.lan' 'seafile.{}'.format(BASE_DOMAIN)
) )
SAAS_SEAFILE_DEFAULT_QUOTA = getattr(settings, 'SAAS_SEAFILE_DEFAULT_QUOTA', SAAS_SEAFILE_DEFAULT_QUOTA = getattr(settings, 'SAAS_SEAFILE_DEFAULT_QUOTA',
50 50
) )
SAAS_BSCW_DOMAIN = getattr(settings, 'SAAS_BSCW_DOMAIN', SAAS_BSCW_DOMAIN = getattr(settings, 'SAAS_BSCW_DOMAIN',
'bscw.orchestra.lan' 'bscw.{}'.format(BASE_DOMAIN)
) )
SAAS_BSCW_DEFAULT_QUOTA = getattr(settings, 'SAAS_BSCW_DEFAULT_QUOTA', SAAS_BSCW_DEFAULT_QUOTA = getattr(settings, 'SAAS_BSCW_DEFAULT_QUOTA',
50 50
) )
SAAS_BSCW_BSADMIN_PATH = getattr(settings, 'SAAS_BSCW_BSADMIN_PATH',
'/home/httpd/bscw/bin/bsadmin',
)
SAAS_GITLAB_ROOT_PASSWORD = getattr(settings, 'SAAS_GITLAB_ROOT_PASSWORD', SAAS_GITLAB_ROOT_PASSWORD = getattr(settings, 'SAAS_GITLAB_ROOT_PASSWORD',
'secret' 'secret'
) )
SAAS_GITLAB_DOMAIN = getattr(settings, 'SAAS_GITLAB_DOMAIN', SAAS_GITLAB_DOMAIN = getattr(settings, 'SAAS_GITLAB_DOMAIN',
'gitlab.orchestra.lan' 'gitlab.{}'.format(BASE_DOMAIN)
) )

View File

@ -37,6 +37,7 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
) )
actions = [update_orders, clone] actions = [update_orders, clone]
change_view_actions = actions + [view_help] change_view_actions = actions + [view_help]
change_form_template = 'admin/services/service/change_form.html'
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Improve performance of account field and filter by account """ """ Improve performance of account field and filter by account """

View File

@ -36,8 +36,8 @@ class ServiceHandler(plugins.Plugin):
return getattr(self.service, attr) return getattr(self.service, attr)
@classmethod @classmethod
def get_plugin_choices(cls): def get_choices(cls):
choices = super(ServiceHandler, cls).get_plugin_choices() choices = super(ServiceHandler, cls).get_choices()
return [('', _("Default"))] + choices return [('', _("Default"))] + choices
def validate_content_type(self, service): def validate_content_type(self, service):

View File

@ -7,12 +7,13 @@ from django.db.models import Q
from django.db.models.loading import get_model from django.db.models.loading import get_model
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.module_loading import autodiscover_modules from django.utils.module_loading import autodiscover_modules
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat, ugettext_lazy as _
from orchestra.core import caches, validators from orchestra.core import caches, validators
from orchestra.core.translations import ModelTranslation from orchestra.core.translations import ModelTranslation
from orchestra.core.validators import validate_name from orchestra.core.validators import validate_name
from orchestra.models import queryset from orchestra.models import queryset
from orchestra.utils.python import import_class
from . import settings from . import settings
from .handlers import ServiceHandler from .handlers import ServiceHandler
@ -20,6 +21,8 @@ from .handlers import ServiceHandler
autodiscover_modules('handlers') autodiscover_modules('handlers')
rate_class = import_class(settings.SERVICES_RATE_CLASS)
class Service(models.Model): class Service(models.Model):
NEVER = '' NEVER = ''
@ -61,7 +64,7 @@ class Service(models.Model):
help_text=_("Handler used for processing this Service. A handler " help_text=_("Handler used for processing this Service. A handler "
"enables customized behaviour far beyond what options " "enables customized behaviour far beyond what options "
"here allow to."), "here allow to."),
choices=ServiceHandler.get_plugin_choices()) choices=ServiceHandler.get_choices())
is_active = models.BooleanField(_("active"), default=True) is_active = models.BooleanField(_("active"), default=True)
ignore_superusers = models.BooleanField(_("ignore superusers"), default=True, ignore_superusers = models.BooleanField(_("ignore superusers"), default=True,
help_text=_("Designates whether superuser orders are marked as ignored by default or not.")) help_text=_("Designates whether superuser orders are marked as ignored by default or not."))
@ -128,13 +131,10 @@ class Service(models.Model):
), ),
default=BILLING_PERIOD) default=BILLING_PERIOD)
rate_algorithm = models.CharField(_("rate algorithm"), max_length=16, rate_algorithm = models.CharField(_("rate algorithm"), max_length=16,
help_text=_("Algorithm used to interprete the rating table."), help_text=string_concat(_("Algorithm used to interprete the rating table."), *[
# TODO this should be dynamic, retrieved from rate (plans) app string_concat('<br>&nbsp;&nbsp;', method.verbose_name, ': ', method.help_text)
choices=( for name, method in rate_class.get_methods().iteritems()
('STEP_PRICE', _("Step price")), ]), choices=rate_class.get_choices(), default=rate_class.get_choices()[0][0])
('MATCH_PRICE', _("Match price")),
),
default='STEP_PRICE')
on_cancel = models.CharField(_("on cancel"), max_length=16, on_cancel = models.CharField(_("on cancel"), max_length=16,
help_text=_("Defines the cancellation behaviour of this service."), help_text=_("Defines the cancellation behaviour of this service."),
choices=( choices=(
@ -171,7 +171,7 @@ class Service(models.Model):
def handler(self): def handler(self):
""" Accessor of this service handler instance """ """ Accessor of this service handler instance """
if self.handler_type: if self.handler_type:
return ServiceHandler.get_plugin(self.handler_type)(self) return ServiceHandler.get(self.handler_type)(self)
return ServiceHandler(self) return ServiceHandler(self)
def clean(self): def clean(self):
@ -231,8 +231,7 @@ class Service(models.Model):
@property @property
def rate_method(self): def rate_method(self):
rate_model = type(self).rates.related.model return rate_class.get_methods()[self.rate_algorithm]
return rate_model.get_methods()[self.rate_algorithm]
def update_orders(self, commit=True): def update_orders(self, commit=True):
order_model = get_model(settings.SERVICES_ORDER_MODEL) order_model = get_model(settings.SERVICES_ORDER_MODEL)

View File

@ -9,13 +9,27 @@ SERVICES_SERVICE_TAXES = getattr(settings, 'SERVICES_SERVICE_TAXES', (
(21, "21%"), (21, "21%"),
)) ))
SERVICES_SERVICE_DEFAULT_TAX = getattr(settings, 'SERVICES_SERVICE_DEFAULT_TAX', 0)
SERVICES_SERVICE_DEFAULT_TAX = getattr(settings, 'SERVICES_SERVICE_DEFAULT_TAX',
0
)
SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH', 1) SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH',
1
)
SERVICES_ORDER_MODEL = getattr(settings, 'SERVICES_ORDER_MODEL', 'orders.Order') SERVICES_ORDER_MODEL = getattr(settings, 'SERVICES_ORDER_MODEL',
'orders.Order'
)
SERVICES_DEFAULT_IGNORE_PERIOD = getattr(settings, 'SERVICES_DEFAULT_IGNORE_PERIOD', 'TEN_DAYS') SERVICES_RATE_CLASS = getattr(settings, 'SERVICES_RATE_CLASS',
'orchestra.apps.plans.models.Rate'
)
SERVICES_DEFAULT_IGNORE_PERIOD = getattr(settings, 'SERVICES_DEFAULT_IGNORE_PERIOD',
'TEN_DAYS'
)

View File

@ -0,0 +1,52 @@
{% extends "orchestra/admin/change_form.html" %}
{% load i18n admin_urls admin_static admin_modify %}
{% block object-tools %}
{% if add %}
<ul class="object-tools">
<li><select name="forma" onchange="location = this.options[this.selectedIndex].value;" style="margin: -3px 0 0 0;">
<option selected disabled>{% trans "Templates" %}</option>
<option value="./?description=Mailbox&
content_type=10&
match=mailbox.active&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=ANUAL&
billing_point=ON_FIXED_DATE&
is_fee=&
order_description=&
ignore_period=TEN_DAYS&
metric=&
nominal_price=28.10&
tax=21&
pricing_period=BILLING_PERIOD&
rate_algorithm=MATCH_PRICE&
on_cancel=COMPENSATE&
payment_style=PREPAY">Mailbox</option>
<option value="./?description=Database&
content_type=19&
match=database.account.is_active&handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=ANUAL&
billing_point=ON_FIXED_DATE&
is_fee=&
order_description=&
ignore_period=TEN_DAYS&
metric=&
nominal_price=24.79&
tax=21&
pricing_period=BILLING_PERIOD&
rate_algorithm=STEP_PRICE&
on_cancel=COMPENSATE&
payment_style=PREPAY">Database</option>
</select></li>
<li>
<a href="./help" class="historylink">{% trans "Help" %}</a>
</li>
</ul>
{% endif %}
{{ block.super }}
{% endblock %}

View File

@ -11,7 +11,9 @@ SYSTEMUSERS_SHELLS = getattr(settings, 'SYSTEMUSERS_SHELLS', (
)) ))
SYSTEMUSERS_DEFAULT_SHELL = getattr(settings, 'SYSTEMUSERS_DEFAULT_SHELL', '/dev/null') SYSTEMUSERS_DEFAULT_SHELL = getattr(settings, 'SYSTEMUSERS_DEFAULT_SHELL',
'/dev/null'
)
SYSTEMUSERS_DISABLED_SHELLS = getattr(settings, 'SYSTEMUSERS_DISABLED_SHELLS', ( SYSTEMUSERS_DISABLED_SHELLS = getattr(settings, 'SYSTEMUSERS_DISABLED_SHELLS', (
@ -20,11 +22,16 @@ SYSTEMUSERS_DISABLED_SHELLS = getattr(settings, 'SYSTEMUSERS_DISABLED_SHELLS', (
)) ))
SYSTEMUSERS_HOME = getattr(settings, 'SYSTEMUSERS_HOME', '/home/./%(user)s') SYSTEMUSERS_HOME = getattr(settings, 'SYSTEMUSERS_HOME',
'/home/%(user)s'
)
SYSTEMUSERS_FTP_LOG_PATH = getattr(settings, 'SYSTEMUSERS_FTP_LOG_PATH', '/var/log/vsftpd.log') SYSTEMUSERS_FTP_LOG_PATH = getattr(settings, 'SYSTEMUSERS_FTP_LOG_PATH',
'/var/log/vsftpd.log'
)
SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = getattr(settings, 'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS', SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = getattr(settings, 'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
('www-data',)) ('www-data',)
)

View File

@ -6,7 +6,9 @@ VPS_TYPES = getattr(settings, 'VPS_TYPES', (
)) ))
VPS_DEFAULT_TYPE = getattr(settings, 'VPS_DEFAULT_TYPE', 'openvz') VPS_DEFAULT_TYPE = getattr(settings, 'VPS_DEFAULT_TYPE',
'openvz'
)
VPS_TEMPLATES = getattr(settings, 'VPS_TEMPLATES', ( VPS_TEMPLATES = getattr(settings, 'VPS_TEMPLATES', (
@ -14,4 +16,6 @@ VPS_TEMPLATES = getattr(settings, 'VPS_TEMPLATES', (
)) ))
VPS_DEFAULT_TEMPLATE = getattr(settings, 'VPS_DEFAULT_TEMPLATE', 'debian7') VPS_DEFAULT_TEMPLATE = getattr(settings, 'VPS_DEFAULT_TEMPLATE',
'debian7'
)

View File

@ -36,7 +36,7 @@ class WebAppOptionInline(admin.TabularInline):
plugin = self.parent_object.type_class plugin = self.parent_object.type_class
else: else:
request = kwargs['request'] request = kwargs['request']
plugin = AppType.get_plugin(request.GET['type']) plugin = AppType.get(request.GET['type'])
kwargs['choices'] = plugin.get_options_choices() kwargs['choices'] = plugin.get_options_choices()
# Help text based on select widget # Help text based on select widget
target = 'this.id.replace("name", "value")' target = 'this.id.replace("name", "value")'

View File

@ -22,7 +22,7 @@ class WebApp(models.Model):
""" Represents a web application """ """ Represents a web application """
name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name]) name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name])
type = models.CharField(_("type"), max_length=32, type = models.CharField(_("type"), max_length=32,
choices=AppType.get_plugin_choices()) choices=AppType.get_choices())
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
related_name='webapps') related_name='webapps')
data = JSONField(_("data"), blank=True, default={}, data = JSONField(_("data"), blank=True, default={},
@ -45,7 +45,7 @@ class WebApp(models.Model):
@cached_property @cached_property
def type_class(self): def type_class(self):
return AppType.get_plugin(self.type) return AppType.get(self.type)
@cached_property @cached_property
def type_instance(self): def type_instance(self):
@ -103,7 +103,7 @@ class WebAppOption(models.Model):
@cached_property @cached_property
def option_class(self): def option_class(self):
return AppOption.get_plugin(self.name) return AppOption.get(self.name)
@cached_property @cached_property
def option_instance(self): def option_instance(self):

View File

@ -1,9 +1,12 @@
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.settings import BASE_DOMAIN
WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT',
'%(home)s/webapps/%(app_name)s/') '%(home)s/webapps/%(app_name)s/'
)
WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN', WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
@ -17,16 +20,19 @@ WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH',
WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH', WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH',
# Inside SuExec Document root # Inside SuExec Document root
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper') '/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper'
)
WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PATH', WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PATH',
# Loaded by Apache # Loaded by Apache
'/etc/apache2/fcgid-conf/%(user)s-%(app_name)s.conf') '/etc/apache2/fcgid-conf/%(user)s-%(app_name)s.conf'
)
WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH', WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH',
'') ''
)
WEBAPPS_MERGE_PHP_WEBAPPS = getattr(settings, 'WEBAPPS_MERGE_PHP_WEBAPPS', WEBAPPS_MERGE_PHP_WEBAPPS = getattr(settings, 'WEBAPPS_MERGE_PHP_WEBAPPS',
@ -55,29 +61,35 @@ WEBAPPS_PHP_VERSIONS = getattr(settings, 'WEBAPPS_PHP_VERSIONS', (
WEBAPPS_DEFAULT_PHP_VERSION = getattr(settings, 'WEBAPPS_DEFAULT_PHP_VERSION', WEBAPPS_DEFAULT_PHP_VERSION = getattr(settings, 'WEBAPPS_DEFAULT_PHP_VERSION',
'5.4-cgi') '5.4-cgi'
)
WEBAPPS_PHP_CGI_BINARY_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_BINARY_PATH', WEBAPPS_PHP_CGI_BINARY_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_BINARY_PATH',
# Path of the cgi binary used by fcgid # Path of the cgi binary used by fcgid
'/usr/bin/php%(php_version_number)s-cgi') '/usr/bin/php%(php_version_number)s-cgi'
)
WEBAPPS_PHP_CGI_RC_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_RC_DIR', WEBAPPS_PHP_CGI_RC_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_RC_DIR',
# Path to php.ini # Path to php.ini
'/etc/php%(php_version_number)s/cgi/') '/etc/php%(php_version_number)s/cgi/'
)
WEBAPPS_PHP_CGI_INI_SCAN_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_INI_SCAN_DIR', WEBAPPS_PHP_CGI_INI_SCAN_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_INI_SCAN_DIR',
# Path to php.ini # Path to php.ini
'/etc/php%(php_version_number)s/cgi/conf.d') '/etc/php%(php_version_number)s/cgi/conf.d'
)
WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH', WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH',
# Server-side path where a under construction stock page is # Server-side path where a under construction stock page is
# '/var/www/undercontruction/index.html', # '/var/www/undercontruction/index.html',
'') ''
)
#WEBAPPS_TYPES_OVERRIDE = getattr(settings, 'WEBAPPS_TYPES_OVERRIDE', {}) #WEBAPPS_TYPES_OVERRIDE = getattr(settings, 'WEBAPPS_TYPES_OVERRIDE', {})
#for webapp_type, value in WEBAPPS_TYPES_OVERRIDE.iteritems(): #for webapp_type, value in WEBAPPS_TYPES_OVERRIDE.iteritems():
@ -151,4 +163,5 @@ WEBAPPS_ENABLED_OPTIONS = getattr(settings, 'WEBAPPS_ENABLED_OPTIONS', (
WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST = getattr(settings, 'WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST', WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST = getattr(settings, 'WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST',
'mysql.orchestra.lan') 'mysql.{}'.format(BASE_DOMAIN)
)

View File

@ -210,8 +210,8 @@ class Apache2Backend(ServiceController):
location, target = proxy.split() location, target = proxy.split()
location = normurlpath(source) location = normurlpath(source)
proxy = textwrap.dedent("""\ proxy = textwrap.dedent("""\
ProxyPass {location} {target} ProxyPass {location}/ {target}
ProxyPassReverse {location} {target}""".format( ProxyPassReverse {location}/ {target}""".format(
location=location, target=target) location=location, target=target)
) )
proxies.append((location, proxy)) proxies.append((location, proxy))

View File

@ -40,7 +40,7 @@ class SiteDirective(Plugin):
return groups return groups
@classmethod @classmethod
def get_plugin_choices(cls): def get_choices(cls):
""" Generates grouped choices ready to use in Field.choices """ """ Generates grouped choices ready to use in Field.choices """
# generators can not be @cached # generators can not be @cached
yield (None, '-------') yield (None, '-------')

View File

@ -104,7 +104,7 @@ class WebsiteDirective(models.Model):
website = models.ForeignKey(Website, verbose_name=_("web site"), website = models.ForeignKey(Website, verbose_name=_("web site"),
related_name='directives') related_name='directives')
name = models.CharField(_("name"), max_length=128, name = models.CharField(_("name"), max_length=128,
choices=SiteDirective.get_plugin_choices()) choices=SiteDirective.get_choices())
value = models.CharField(_("value"), max_length=256) value = models.CharField(_("value"), max_length=256)
def __unicode__(self): def __unicode__(self):
@ -112,7 +112,7 @@ class WebsiteDirective(models.Model):
@cached_property @cached_property
def directive_class(self): def directive_class(self):
return SiteDirective.get_plugin(self.name) return SiteDirective.get(self.name)
@cached_property @cached_property
def directive_instance(self): def directive_instance(self):

View File

@ -3,7 +3,8 @@ from django.utils.translation import ugettext_lazy as _
WEBSITES_UNIQUE_NAME_FORMAT = getattr(settings, 'WEBSITES_UNIQUE_NAME_FORMAT', WEBSITES_UNIQUE_NAME_FORMAT = getattr(settings, 'WEBSITES_UNIQUE_NAME_FORMAT',
'%(user)s-%(site_name)s') '%(user)s-%(site_name)s'
)
# TODO 'http', 'https', 'https-only', 'http and https' and rename to PROTOCOL # TODO 'http', 'https', 'https-only', 'http and https' and rename to PROTOCOL
@ -20,15 +21,19 @@ WEBSITES_PROTOCOL_CHOICES = getattr(settings, 'WEBSITES_PROTOCOL_CHOICES', (
('https-only', _("HTTPS only")), ('https-only', _("HTTPS only")),
)) ))
WEBSITES_DEFAULT_PROTOCOL = getattr(settings, 'WEBSITES_DEFAULT_PROTOCOL', 'http') WEBSITES_DEFAULT_PROTOCOL = getattr(settings, 'WEBSITES_DEFAULT_PROTOCOL',
'http'
#WEBSITES_DEFAULT_PORT = getattr(settings, 'WEBSITES_DEFAULT_PORT', 80) )
WEBSITES_DEFAULT_IP = getattr(settings, 'WEBSITES_DEFAULT_IP', '*') WEBSITES_DEFAULT_IP = getattr(settings, 'WEBSITES_DEFAULT_IP',
'*'
)
WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL', 'domains.Domain') WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL',
'domains.Domain'
)
WEBSITES_ENABLED_DIRECTIVES = getattr(settings, 'WEBSITES_ENABLED_DIRECTIVES', ( WEBSITES_ENABLED_DIRECTIVES = getattr(settings, 'WEBSITES_ENABLED_DIRECTIVES', (
@ -47,23 +52,28 @@ WEBSITES_ENABLED_DIRECTIVES = getattr(settings, 'WEBSITES_ENABLED_DIRECTIVES', (
WEBSITES_BASE_APACHE_CONF = getattr(settings, 'WEBSITES_BASE_APACHE_CONF', WEBSITES_BASE_APACHE_CONF = getattr(settings, 'WEBSITES_BASE_APACHE_CONF',
'/etc/apache2/') '/etc/apache2/'
)
WEBSITES_WEBALIZER_PATH = getattr(settings, 'WEBSITES_WEBALIZER_PATH', WEBSITES_WEBALIZER_PATH = getattr(settings, 'WEBSITES_WEBALIZER_PATH',
'/home/httpd/webalizer/') '/home/httpd/webalizer/'
)
WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH', WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH',
'/var/log/apache2/virtual/%(unique_name)s.log') '/var/log/apache2/virtual/%(unique_name)s.log'
)
WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH', WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH',
'') ''
)
WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS', WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS',
('127.0.0.1',)) ('127.0.0.1',)
)
#WEBSITES_DEFAULT_SSl_CA = getattr(settings, 'WEBSITES_DEFAULT_SSl_CA', #WEBSITES_DEFAULT_SSl_CA = getattr(settings, 'WEBSITES_DEFAULT_SSl_CA',

View File

@ -3,34 +3,40 @@ import os
from django.core.management.commands import makemessages from django.core.management.commands import makemessages
from orchestra.core.translations import ModelTranslation from orchestra.core.translations import ModelTranslation
from orchestra.utils.paths import get_site_root from orchestra.utils.paths import get_site_dir
class Command(makemessages.Command): class Command(makemessages.Command):
""" Provides database translations support """ """ Provides database translations support """
def handle(self, *args, **options): def handle(self, *args, **options):
do_database = os.getcwd() == get_site_root() self.database_files = []
self.generated_database_files = [] try:
if do_database: if os.getcwd() == get_site_dir():
self.project_locale_path = get_site_root() self.generate_database_files()
self.generate_database_files() super(Command, self).handle(*args, **options)
super(Command, self).handle(*args, **options) finally:
self.remove_database_files() self.remove_database_files()
def get_contents(self): def get_contents(self):
for model, fields in ModelTranslation._registry.iteritems(): for model, fields in ModelTranslation._registry.iteritems():
contents = []
for field in fields: for field in fields:
contents = []
for content in model.objects.values_list('id', field): for content in model.objects.values_list('id', field):
pk, value = content pk, value = content
contents.append( contents.append(
(pk, u"_(u'%s')" % value) (pk, u"_(u'%s')" % value)
) )
yield ('_'.join((model._meta.db_table, field)), contents) if contents:
yield ('_'.join((model._meta.db_table, field)), contents)
def generate_database_files(self): def generate_database_files(self):
""" tmp files are generated because of having a nice gettext location """ """
Tmp files are generated because:
1) having a nice gettext location
# database_db_table_field.sql.py:id
2) Django's makemessages will work with no modifications
"""
for name, contents in self.get_contents(): for name, contents in self.get_contents():
name = unicode(name) name = unicode(name)
maximum = None maximum = None
@ -43,11 +49,11 @@ class Command(makemessages.Command):
for ix in xrange(maximum+1): for ix in xrange(maximum+1):
tmpcontent.append(content.get(ix, '')) tmpcontent.append(content.get(ix, ''))
tmpcontent = u'\n'.join(tmpcontent) + '\n' tmpcontent = u'\n'.join(tmpcontent) + '\n'
filepath = os.path.join(self.project_locale_path, 'database_%s.sql.py' % name) filename = 'database_%s.sql.py' % name
self.generated_database_files.append(filepath) self.database_files.append(filename)
with open(filepath, 'w') as tmpfile: with open(filename, 'w') as tmpfile:
tmpfile.write(tmpcontent.encode('utf-8')) tmpfile.write(tmpcontent.encode('utf-8'))
def remove_database_files(self): def remove_database_files(self):
for path in self.generated_database_files: for path in self.database_files:
os.unlink(path) os.unlink(path)

View File

@ -4,7 +4,7 @@ from optparse import make_option
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from orchestra.utils.paths import get_site_root from orchestra.utils.paths import get_site_dir
from orchestra.utils.system import run, check_root from orchestra.utils.system import run, check_root
@ -66,7 +66,7 @@ class Command(BaseCommand):
run('chmod +x %s' % orchestra_admin) run('chmod +x %s' % orchestra_admin)
run("%s install_requirements" % orchestra_admin) run("%s install_requirements" % orchestra_admin)
manage_path = os.path.join(get_site_root(), 'manage.py') manage_path = os.path.join(get_site_dir(), 'manage.py')
run("python %s collectstatic --noinput" % manage_path) run("python %s collectstatic --noinput" % manage_path)
run("python %s syncdb --noinput" % manage_path) run("python %s syncdb --noinput" % manage_path)
run("python %s migrate --noinput" % manage_path) run("python %s migrate --noinput" % manage_path)

View File

@ -4,7 +4,7 @@ from os import path
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from orchestra.utils.paths import get_site_root, get_orchestra_root from orchestra.utils.paths import get_site_dir, get_orchestra_dir
from orchestra.utils.system import run, check_root from orchestra.utils.system import run, check_root
@ -28,9 +28,9 @@ class Command(BaseCommand):
@check_root @check_root
def handle(self, *args, **options): def handle(self, *args, **options):
context = { context = {
'site_root': get_site_root(), 'site_dir': get_site_dir(),
'username': options.get('username'), 'username': options.get('username'),
'bin_path': path.join(get_orchestra_root(), 'bin'), 'bin_path': path.join(get_orchestra_dir(), 'bin'),
'processes': options.get('processes'), 'processes': options.get('processes'),
} }
@ -39,7 +39,7 @@ class Command(BaseCommand):
CELERYD_NODES="w1" CELERYD_NODES="w1"
# Where to chdir at start. # Where to chdir at start.
CELERYD_CHDIR="%(site_root)s" CELERYD_CHDIR="%(site_dir)s"
# How to call "manage.py celeryd_multi" # How to call "manage.py celeryd_multi"
CELERYD_MULTI="$CELERYD_CHDIR/manage.py celeryd_multi" CELERYD_MULTI="$CELERYD_CHDIR/manage.py celeryd_multi"

View File

@ -5,7 +5,7 @@ from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils.six.moves import input from django.utils.six.moves import input
from orchestra.utils.paths import get_project_root, get_site_root, get_project_name from orchestra.utils.paths import get_project_dir, get_site_dir, get_project_name
from orchestra.utils.system import run, check_root, get_default_celeryd_username from orchestra.utils.system import run, check_root, get_default_celeryd_username
@ -34,8 +34,8 @@ class Command(BaseCommand):
context = { context = {
'project_name': get_project_name(), 'project_name': get_project_name(),
'project_root': get_project_root(), 'project_dir': get_project_dir(),
'site_root': get_site_root(), 'site_dir': get_site_dir(),
'static_root': settings.STATIC_ROOT, 'static_root': settings.STATIC_ROOT,
'user': options.get('user'), 'user': options.get('user'),
'group': options.get('group') or options.get('user'), 'group': options.get('group') or options.get('user'),
@ -61,7 +61,7 @@ class Command(BaseCommand):
uwsgi_conf = ( uwsgi_conf = (
'[uwsgi]\n' '[uwsgi]\n'
'plugins = python\n' 'plugins = python\n'
'chdir = %(site_root)s\n' 'chdir = %(site_dir)s\n'
'module = %(project_name)s.wsgi\n' 'module = %(project_name)s.wsgi\n'
'master = true\n' 'master = true\n'
'processes = %(processes)d\n' 'processes = %(processes)d\n'

View File

@ -3,7 +3,7 @@ from optparse import make_option
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from orchestra.utils.paths import get_project_root from orchestra.utils.paths import get_project_dir
from orchestra.utils.system import run, check_root from orchestra.utils.system import run, check_root
@ -42,7 +42,7 @@ class Command(BaseCommand):
run('su postgres -c "psql -c \\"CREATE USER %(db_user)s PASSWORD \'%(db_password)s\';\\""' % context, error_codes=[0,1]) run('su postgres -c "psql -c \\"CREATE USER %(db_user)s PASSWORD \'%(db_password)s\';\\""' % context, error_codes=[0,1])
run('su postgres -c "psql -c \\"CREATE DATABASE %(db_name)s OWNER %(db_user)s;\\""' % context, error_codes=[0,1]) run('su postgres -c "psql -c \\"CREATE DATABASE %(db_name)s OWNER %(db_user)s;\\""' % context, error_codes=[0,1])
context.update({'settings': os.path.join(get_project_root(), 'settings.py')}) context.update({'settings': os.path.join(get_project_dir(), 'settings.py')})
if run("grep 'DATABASES' %(settings)s" % context, error_codes=[0,1]).return_code == 0: if run("grep 'DATABASES' %(settings)s" % context, error_codes=[0,1]).return_code == 0:
# Update existing settings_file # Update existing settings_file

View File

@ -7,7 +7,7 @@ import sys
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from pyflakes import checker, messages from pyflakes import checker, messages
from orchestra.utils.paths import get_orchestra_root from orchestra.utils.paths import get_orchestra_dir
# BlackHole, PySyntaxError and checking based on # BlackHole, PySyntaxError and checking based on
@ -93,7 +93,7 @@ class Command(BaseCommand):
def handle(self, *filenames, **options): def handle(self, *filenames, **options):
if not filenames: if not filenames:
filenames = [get_orchestra_root(), '.'] filenames = [get_orchestra_dir(), '.']
warnings = checkPaths(filenames) warnings = checkPaths(filenames)
for warning in warnings: for warning in warnings:
print warning print warning

View File

@ -17,7 +17,7 @@ class SelectPluginAdminMixin(object):
plugin = getattr(obj, '%s_instance' % self.plugin_field) plugin = getattr(obj, '%s_instance' % self.plugin_field)
self.form = getattr(plugin, 'get_change_form', plugin.get_form)() self.form = getattr(plugin, 'get_change_form', plugin.get_form)()
else: else:
plugin = self.plugin.get_plugin(self.plugin_value)() plugin = self.plugin.get(self.plugin_value)()
self.form = plugin.get_form() self.form = plugin.get_form()
return super(SelectPluginAdminMixin, self).get_form(request, obj, **kwargs) return super(SelectPluginAdminMixin, self).get_form(request, obj, **kwargs)
@ -67,7 +67,7 @@ class SelectPluginAdminMixin(object):
self.plugin_value = plugin_value self.plugin_value = plugin_value
if not plugin_value: if not plugin_value:
self.plugin_value = self.plugin.get_plugins()[0].get_name() self.plugin_value = self.plugin.get_plugins()[0].get_name()
plugin = self.plugin.get_plugin(self.plugin_value) plugin = self.plugin.get(self.plugin_value)
context = { context = {
'title': _("Add new %s") % plugin.verbose_name, 'title': _("Add new %s") % plugin.verbose_name,
} }

View File

@ -27,7 +27,7 @@ class Plugin(object):
return cls.plugins return cls.plugins
@classmethod @classmethod
def get_plugin(cls, name): def get(cls, name):
if not hasattr(cls, '_registry'): if not hasattr(cls, '_registry'):
cls._registry = { cls._registry = {
plugin.get_name(): plugin for plugin in cls.get_plugins() plugin.get_name(): plugin for plugin in cls.get_plugins()
@ -44,7 +44,7 @@ class Plugin(object):
return cls.get_name() return cls.get_name()
@classmethod @classmethod
def get_plugin_choices(cls): def get_choices(cls):
choices = [] choices = []
for plugin in cls.get_plugins(): for plugin in cls.get_plugins():
verbose = plugin.get_verbose_name() verbose = plugin.get_verbose_name()
@ -102,7 +102,7 @@ class PluginModelAdapter(Plugin):
return plugins return plugins
@classmethod @classmethod
def get_plugin(cls, name): def get(cls, name):
# don't cache, since models can change # don't cache, since models can change
for plugin in cls.get_plugins(): for plugin in cls.get_plugins():
if name == plugin.get_name(): if name == plugin.get_name():

View File

@ -4,32 +4,55 @@ from django.utils.translation import ugettext_lazy as _
# Domain name used when it will not be possible to infere the domain from a request # Domain name used when it will not be possible to infere the domain from a request
# For example in periodic tasks # For example in periodic tasks
SITE_URL = getattr(settings, 'SITE_URL', 'http://localhost') SITE_URL = getattr(settings, 'SITE_URL',
'http://localhost'
)
SITE_NAME = getattr(settings, 'SITE_NAME',
'orchestra'
)
SITE_NAME = getattr(settings, 'SITE_NAME', 'confine')
SITE_VERBOSE_NAME = getattr(settings, 'SITE_VERBOSE_NAME', SITE_VERBOSE_NAME = getattr(settings, 'SITE_VERBOSE_NAME',
_("%s Hosting Management" % SITE_NAME.capitalize())) _("%s Hosting Management" % SITE_NAME.capitalize())
)
BASE_DOMAIN = getattr(settings, 'BASE_DOMAIN',
'orchestra.lan'
)
# Service management commands # Service management commands
START_SERVICES = getattr(settings, 'START_SERVICES', START_SERVICES = getattr(settings, 'START_SERVICES', [
['postgresql', 'celeryevcam', 'celeryd', 'celerybeat', ('uwsgi', 'nginx'),] 'postgresql',
'celeryevcam',
'celeryd',
'celerybeat',
('uwsgi', 'nginx'),
])
RESTART_SERVICES = getattr(settings, 'RESTART_SERVICES', [
'celeryd',
'celerybeat',
'uwsgi'
])
STOP_SERVICES = getattr(settings, 'STOP_SERVICES', [
('uwsgi', 'nginx'),
'celerybeat',
'celeryd',
'celeryevcam',
'postgresql'
])
API_ROOT_VIEW = getattr(settings, 'API_ROOT_VIEW',
'orchestra.api.root.APIRoot'
) )
RESTART_SERVICES = getattr(settings, 'RESTART_SERVICES',
['celeryd', 'celerybeat', 'uwsgi']
)
STOP_SERVICES = getattr(settings, 'STOP_SERVICES',
[('uwsgi', 'nginx'), 'celerybeat', 'celeryd', 'celeryevcam', 'postgresql']
)
API_ROOT_VIEW = getattr(settings, 'API_ROOT_VIEW', 'orchestra.api.root.APIRoot')
ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL = getattr(settings, 'ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL', ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL = getattr(settings, 'ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL',
'support@orchestra.lan' 'support@{}'.format(BASE_DOMAIN)
) )

View File

@ -1,7 +1,7 @@
import os import os
def get_project_root(): def get_project_dir():
""" Return the current project path site/project """ """ Return the current project path site/project """
from django.conf import settings from django.conf import settings
settings_file = os.sys.modules[settings.SETTINGS_MODULE].__file__ settings_file = os.sys.modules[settings.SETTINGS_MODULE].__file__
@ -10,15 +10,15 @@ def get_project_root():
def get_project_name(): def get_project_name():
""" Returns current project name """ """ Returns current project name """
return os.path.basename(get_project_root()) return os.path.basename(get_project_dir())
def get_site_root(): def get_site_dir():
""" Returns project site path """ """ Returns project site path """
return os.path.abspath(os.path.join(get_project_root(), '..')) return os.path.abspath(os.path.join(get_project_dir(), '..'))
def get_orchestra_root(): def get_orchestra_dir():
""" Returns orchestra base path """ """ Returns orchestra base path """
import orchestra import orchestra
return os.path.dirname(os.path.realpath(orchestra.__file__)) return os.path.dirname(os.path.realpath(orchestra.__file__))