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
* LAST version of this shit http://wkhtmltopdf.org/downloads.html
* LAST version of this shit http://wkhtmltopdf.org/downloads.h otml
* translations
from django.utils import translation
@ -137,17 +137,15 @@
* Resource graph for each related object
* Service.account change and orders consistency
* 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
* forms autocomplete="off", doesn't work in chrome
ln -s /proc/self/fd /dev/fd
* escape passwords and not allow ' on them !
POST INSTALL
@ -160,15 +158,12 @@ ssh-copy-id root@<server-address>
Php binaries should have this format: /usr/bin/php5.2-cgi
* logs on panel/logs/ ? mkdir ~webapps, backend post save signal?
* transaction fault tolerant on backend.execute()
* <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
* 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
@ -223,7 +218,7 @@ require_once(/etc/moodles/.$moodle_host.config.php);``` moodle/drupl
* autoexpand mailbox.filter according to filtering options
* 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
* 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
* 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
* 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)
* TODO raise404, here and everywhere
* display subline links on billlines
* update service orders on a celery task?
* display subline links on billlines, to show that they exists.
* update service orders on a celery task? because it take alot
*
* billline quantity eval('10x100') instead of miningless description '(10*100)'
* order metric increases inside billed until period
* 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
* 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
* 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
* one time service metric change should update last value, only record for recurring invoicing.
* 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
* (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
-----------
python manage.py makemessages -l ca --domain database
mkdir locale
django-admin.py makemessages -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 import translation
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.utils.translation import ugettext_lazy as _
from orchestra.settings import BASE_DOMAIN
ACCOUNTS_TYPES = getattr(settings, 'ACCOUNTS_TYPES', (
('INDIVIDUAL', _("Individual")),
@ -11,7 +13,10 @@ ACCOUNTS_TYPES = getattr(settings, 'ACCOUNTS_TYPES', (
('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', (
@ -20,13 +25,18 @@ ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
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', (
@ -42,12 +52,13 @@ ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
('domains.Domain',
'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',
'admin/accounts/account/service_report.html')
'admin/accounts/account/service_report.html'
)

View File

@ -22,7 +22,7 @@
{% block object-tools-items %}
{% if services %}
<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 %}
<option value="{% url service|admin_urlname:'changelist' %}?account={{ original.pk }}">{{ service.verbose_name_plural|capfirst }}</option>
{% endfor %}
@ -30,7 +30,7 @@
{% endif %}
{% if accounts %}
<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 %}
<option value="{% url account|admin_urlname:'changelist' %}?account={{ original.pk }}">{{ account.verbose_name_plural|capfirst }}</option>
{% endfor %}

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -20,11 +20,11 @@ msgstr ""
#: actions.py:35
msgid "Download"
msgstr "Blod"
msgstr "Descarrega"
#: actions.py:45
msgid "View"
msgstr ""
msgstr "Vista"
#: actions.py:53
msgid "Selected bills should be in open state"
@ -62,11 +62,11 @@ msgstr ""
msgid "Resend"
msgstr ""
#: actions.py:129 models.py:308
#: actions.py:129 models.py:309
msgid "Not enough information stored for undoing"
msgstr ""
#: actions.py:132 models.py:310
#: actions.py:132 models.py:311
msgid "Dates don't match"
msgstr ""
@ -92,7 +92,7 @@ msgstr ""
#: admin.py:69
msgid "Description"
msgstr ""
msgstr "Descripció"
#: admin.py:77
msgid "Subtotal"
@ -115,37 +115,37 @@ msgstr ""
msgid "lines"
msgstr ""
#: admin.py:152
#: admin.py:152 templates/bills/microspective.html:107
msgid "total"
msgstr ""
#: admin.py:160 models.py:85 models.py:339
#: admin.py:160 models.py:85 models.py:340
msgid "type"
msgstr ""
msgstr "tipus"
#: admin.py:177
msgid "Payment"
msgstr ""
msgstr "Pagament"
#: filters.py:17
msgid "All"
msgstr ""
msgstr "Tot"
#: filters.py:18 models.py:75
msgid "Invoice"
msgstr ""
msgstr "Factura"
#: filters.py:19 models.py:76
msgid "Amendment invoice"
msgstr ""
msgstr "Factura rectificativa"
#: filters.py:20 models.py:77
msgid "Fee"
msgstr ""
msgstr "Quota de soci"
#: filters.py:21
msgid "Amendment fee"
msgstr ""
msgstr "Rectificació de quota de soci"
#: filters.py:22
msgid "Pro-forma"
@ -157,11 +157,11 @@ msgstr ""
#: filters.py:47
msgid "Yes"
msgstr ""
msgstr "Si"
#: filters.py:48
msgid "No"
msgstr ""
msgstr "No"
#: forms.py:9
msgid "Number"
@ -227,7 +227,7 @@ msgstr ""
#: models.py:32
msgid "VAT number"
msgstr ""
msgstr "NIF"
#: models.py:64
msgid "Paid"
@ -285,62 +285,113 @@ msgstr ""
msgid "HTML"
msgstr ""
#: models.py:270
#: models.py:271
msgid "bill"
msgstr ""
#: models.py:271 models.py:336
#: models.py:272 models.py:337 templates/bills/microspective.html:73
msgid "description"
msgstr ""
#: models.py:272
msgid "rate"
msgstr ""
msgstr "descripció"
#: models.py:273
msgid "quantity"
msgstr ""
msgid "rate"
msgstr "tarifa"
#: models.py:274
msgid "quantity"
msgstr "quantitat"
#: models.py:275 templates/bills/microspective.html:76
msgid "subtotal"
msgstr ""
#: models.py:275
#: models.py:276
msgid "tax"
msgstr ""
msgstr "impostos"
#: models.py:281
#: models.py:282
msgid "Informative link back to the order"
msgstr ""
#: models.py:282
#: models.py:283
msgid "order billed"
msgstr ""
#: models.py:283
#: models.py:284
msgid "order billed until"
msgstr ""
#: models.py:284
#: models.py:285
msgid "created"
msgstr ""
#: models.py:286
#: models.py:287
msgid "amended line"
msgstr ""
#: models.py:329
#: models.py:330
msgid "Volume"
msgstr ""
#: models.py:330
#: models.py:331
msgid "Compensation"
msgstr ""
#: models.py:331
#: models.py:332
msgid "Other"
msgstr ""
#: models.py:335
#: models.py:336
msgid "bill line"
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.db import models
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.functional import cached_property
from django.utils.translation import ugettext_lazy as _
@ -191,7 +191,7 @@ class Bill(models.Model):
self.is_sent = True
self.save(update_fields=['is_sent'])
def render(self, payment=False):
def render(self, payment=False, language=None):
if payment is False:
payment = self.account.paymentsources.get_default()
context = Context({
@ -213,7 +213,8 @@ class Bill(models.Model):
template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
template = getattr(settings, template_name, settings.BILLS_DEFAULT_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 />')
return html

View File

@ -1,66 +1,99 @@
from django.conf import settings
from django_countries import data
BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4)
from orchestra.settings import BASE_DOMAIN
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/microspective.html')
'bills/microspective.html'
)
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/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',
'0000 0000 00 00000000 (Orchestra Bank)')
'0000 0000 00 00000000 (Orchestra Bank)'
)
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_DEFAULT_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY', 'ES')
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'
)

View File

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

View File

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

View File

@ -1,4 +1,5 @@
{% extends 'bills/base.html' %}
{% load i18n %}
{% block head %}
<style type="text/css">
@ -69,10 +70,10 @@
{% block content %}
<div id="lines">
<span class="title column-id">id</span>
<span class="title column-description">description</span>
<span class="title column-quantity">hrs/qty</span>
<span class="title column-rate">rate/price</span>
<span class="title column-subtotal">subtotal</span>
<span class="title column-description">{% trans "description" %}</span>
<span class="title column-quantity">{% trans "hrs/qty" %}</span>
<span class="title column-rate">{% trans "rate/price" %}</span>
<span class="title column-subtotal">{% trans "subtotal" %}</span>
<br>
{% for line in lines %}
{% with sublines=line.sublines.all %}
@ -96,14 +97,14 @@
<div id="totals">
<br>&nbsp;<br>
{% 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>
<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>
<br>
{% 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>
<br>
</div>
@ -115,26 +116,31 @@
<div id="footer-column-1">
<div id="comments">
{% if bill.comments %}
<span class="title">COMMENTS</span> {{ bill.comments|linebreaksbr }}
<span class="title">{% trans "COMMENTS" %}</span> {{ bill.comments|linebreaksbr }}
{% endif %}
</div>
</div>
<div id="footer-column-2">
<div id="payment">
<span class="title">PAYMENT</span>
<span class="title">{% trans "PAYMENT" %}</span>
{% if payment.message %}
{{ payment.message | safe }}
{% 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.
Our bank account number is <br>
{% endblocktrans %}
<strong>{{ seller_info.bank_account }}</strong>
{% endif %}
</div>
<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
your message.
{% endblocktrans %}
</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', (
@ -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 orchestra.settings import BASE_DOMAIN
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',
'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',
'/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', (
'10 mail.orchestra.lan.',
'10 mail2.orchestra.lan.',
'10 mail.{}.'.format(BASE_DOMAIN),
'10 mail2.{}.'.format(BASE_DOMAIN),
))
DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', (
'ns1.orchestra.lan.',
'ns2.orchestra.lan.',
'ns1.{}.'.format(BASE_DOMAIN),
'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
# 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):
context = {
'site_root': paths.get_site_root()
'site_dir': paths.get_site_dir()
}
fname = settings.DOMAINS_FORBIDDEN
if fname:
@ -108,12 +108,15 @@ def validate_soa_record(value):
def validate_zone(zone):
""" Ultimate zone file validation using named-checkzone """
zone_name = zone.split()[0][:-1]
path = os.path.join(settings.DOMAINS_CHECKZONE_PATH, zone_name)
with open(path, 'wb') as f:
f.write(zone)
# Don't use /dev/stdin becuase the 'argument list is too long' error
zone_path = os.path.join(settings.DOMAINS_ZONE_VALIDATION_TMP_DIR, zone_name)
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:
errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1]
raise ValidationError(', '.join(errors))

View File

@ -1,7 +1,10 @@
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
LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain')
from orchestra.settings import BASE_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',
'/var/log/mailman/post')
'/var/log/mailman/post'
)
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',
'/etc/postfix/mailman_virtual_aliases')
'/etc/postfix/mailman_virtual_aliases'
)
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.utils.translation import ugettext_lazy as _
MAILBOXES_DOMAIN_MODEL = getattr(settings, 'MAILBOXES_DOMAIN_MODEL', 'domains.Domain')
from orchestra.settings import BASE_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',
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',
'%(orchestra_root)s/bin/sieve-test')
'%(orchestra_root)s/bin/sieve-test'
)
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',
'/etc/postfix/virtual_aliases')
'/etc/postfix/virtual_aliases'
)
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',
'orchestra.lan')
BASE_DOMAIN
)
MAILBOXES_PASSWD_PATH = getattr(settings, 'MAILBOXES_PASSWD_PATH',
'/etc/dovecot/passwd')
'/etc/dovecot/passwd'
)
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',
'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',
'orchestra.lan')
BASE_DOMAIN
)

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ class RouteAdmin(admin.ModelAdmin):
BACKEND_HELP_TEXT = {
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 = {
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
def get_backend(cls, name):
return cls.get_plugin(name)
return cls.get(name)
@classmethod
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
"""
backend = models.CharField(_("backend"), max_length=256,
choices=ServiceBackend.get_plugin_choices())
choices=ServiceBackend.get_choices())
host = models.ForeignKey(Server, verbose_name=_("host"))
match = models.CharField(_("match"), max_length=256, blank=True, default='True',
help_text=_("Python expression used for selecting the targe host, "

View File

@ -24,7 +24,7 @@ class RouterTests(BaseTestCase):
def save(self, instance):
pass
choices = backends.ServiceBackend.get_plugin_choices()
choices = backends.ServiceBackend.get_choices()
Route._meta.get_field_by_name('backend')[0]._choices = choices
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:
metric = format(line.metric, '.2f').rstrip('0').rstrip('.')
size = format(line.size, '.2f').rstrip('0').rstrip('.')
description += " (%s*%s)" % (metric, size)
description += " (%sx%s)" % (metric, size)
return description
def create_sublines(self, line, discounts):

View File

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

View File

@ -3,11 +3,14 @@ from django.conf import settings
# Pluggable backend for bill generation.
ORDERS_BILLING_BACKEND = getattr(settings, 'ORDERS_BILLING_BACKEND',
'orchestra.apps.orders.billing.BillsBackend')
'orchestra.apps.orders.billing.BillsBackend'
)
# 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
@ -26,4 +29,6 @@ ORDERS_EXCLUDED_APPS = getattr(settings, 'ORDERS_EXCLUDED_APPS', (
# Only account for significative changes
# 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
for method, transactions in queryset.group_by('source__method').iteritems():
if method is not None:
method = PaymentMethod.get_plugin(method)
method = PaymentMethod.get(method)
procs = method.process(transactions)
processes += procs
for trans in transactions:

View File

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

View File

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

View File

@ -1,18 +1,23 @@
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',
'Orchestra')
PAYMENTS_DD_CREDITOR_IBAN = getattr(settings, 'PAYMENTS_DD_CREDITOR_IBAN',
'IE98BOFI90393912121212')
PAYMENTS_DD_CREDITOR_BIC = getattr(settings, 'PAYMENTS_DD_CREDITOR_BIC',
'BOFIIE2D')
PAYMENTS_DD_CREDITOR_AT02_ID = getattr(settings, 'PAYMENTS_DD_CREDITOR_AT02_ID',
'InvalidAT02ID')

View File

@ -84,8 +84,15 @@ class Rate(models.Model):
return "{}-{}".format(str(self.price), self.quantity)
@classmethod
def get_methods(self):
return self.RATE_METHODS
def get_methods(cls):
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)

View File

@ -1,5 +1,7 @@
import sys
from django.utils.translation import string_concat, ugettext_lazy as _
from orchestra.utils.python import AttrDict
@ -117,6 +119,8 @@ def step_price(rates, metric):
ix += 1
minimal = min(minimal, (value, result), key=lambda v: v[0])
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):
@ -144,3 +148,5 @@ def match_price(rates, metric):
'price': candidates[0].price,
})]
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.models import queryset, fields
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 . import helpers, tasks
from . import tasks
from .backends import ServiceMonitor
from .methods import DataMethod
from .validators import validate_scale
@ -34,8 +35,8 @@ class Resource(models.Model):
MONTHLY_AVG = 'MONTHLY_AVG'
PERIODS = (
(LAST, _("Last")),
(MONTHLY_SUM, _("Monthly Sum")),
(MONTHLY_AVG, _("Monthly Average")),
(MONTHLY_SUM, _("Monthly sum")),
(MONTHLY_AVG, _("Monthly avg")),
)
_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)
content_type = models.ForeignKey(ContentType,
help_text=_("Model where this resource will be hooked."))
period = models.CharField(_("period"), max_length=16, choices=PERIODS,
default=LAST,
help_text=_("Operation used for aggregating this resource monitored data."))
# TODO rename to aggregation
period = models.CharField(_("aggregation"), max_length=16,
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,
help_text=_("If enabled the resource will not be pre-allocated, "
"but allocated under the application demand"))
@ -69,7 +71,7 @@ class Resource(models.Model):
help_text=_("Crontab for periodic execution. "
"Leave it empty to disable periodic monitoring"))
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."))
is_active = models.BooleanField(_("active"), default=True)
@ -84,6 +86,15 @@ class Resource(models.Model):
def __unicode__(self):
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):
self.verbose_name = self.verbose_name.strip()
if self.on_demand and self.default_allocation:
@ -114,7 +125,7 @@ class Resource(models.Model):
self.sync_periodic_task()
# This only work on tests (multiprocessing used on real deployments)
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):
super(Resource, self).delete(*args, **kwargs)
@ -201,7 +212,16 @@ class ResourceData(models.Model):
return self.resource.unit
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):
if current is None:
@ -218,10 +238,9 @@ class ResourceData(models.Model):
def get_monitor_datasets(self):
resource = self.resource
today = timezone.now()
datasets = []
for monitor in resource.monitors:
path = self.resource.get_model_path(monitor)
path = resource.get_model_path(monitor)
if path == []:
dataset = MonitorData.objects.filter(
monitor=monitor,
@ -239,30 +258,16 @@ class ResourceData(models.Model):
content_type=ct,
object_id__in=pks
)
if resource.period in (resource.MONTHLY_AVG, resource.MONTHLY_SUM):
datasets.append(
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)
datasets.append(
resource.method_instance.filter(dataset)
)
return datasets
class MonitorData(models.Model):
""" Stores monitored data """
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"))
object_id = models.PositiveIntegerField(_("object id"))
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)
for user in users:
if user['username'] == username:
print 'user-exists'
print 'ValidationError: user-exists'
if user['email'] == email:
print 'email-exists'
print 'ValidationError: email-exists'
def validate_creation(self, saas):
self.append(self._validate_creation, saas)

View File

@ -14,7 +14,7 @@ from .services import SoftwareService
class SaaS(models.Model):
service = models.CharField(_("service"), max_length=32,
choices=SoftwareService.get_plugin_choices())
choices=SoftwareService.get_choices())
name = models.CharField(_("Name"), max_length=64,
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
validators=[validators.validate_username])
@ -41,7 +41,7 @@ class SaaS(models.Model):
@cached_property
def service_class(self):
return SoftwareService.get_plugin(self.service)
return SoftwareService.get(self.service)
@cached_property
def service_instance(self):

View File

@ -1,5 +1,7 @@
from django.conf import settings
from orchestra.settings import BASE_DOMAIN
SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
'orchestra.apps.saas.services.moodle.MoodleService',
@ -17,18 +19,22 @@ SAAS_WORDPRESS_ADMIN_PASSWORD = getattr(settings, 'SAAS_WORDPRESSMU_ADMIN_PASSWO
'secret'
)
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',
'/home/httpd/htdocs/wikifarm/template.tar.gz')
'/home/httpd/htdocs/wikifarm/template.tar.gz'
)
SAAS_DOKUWIKI_FARM_PATH = getattr(settings, 'WEBSITES_DOKUWIKI_FARM_PATH',
'/home/httpd/htdocs/wikifarm/farm'
)
SAAS_DRUPAL_SITES_PATH = getattr(settings, 'WEBSITES_DRUPAL_SITES_PATH',
'/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'
)
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',
'seafile.orchestra.lan'
'seafile.{}'.format(BASE_DOMAIN)
)
SAAS_SEAFILE_DEFAULT_QUOTA = getattr(settings, 'SAAS_SEAFILE_DEFAULT_QUOTA',
50
)
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',
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',
'secret'
)
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]
change_view_actions = actions + [view_help]
change_form_template = 'admin/services/service/change_form.html'
def formfield_for_dbfield(self, db_field, **kwargs):
""" Improve performance of account field and filter by account """

View File

@ -36,8 +36,8 @@ class ServiceHandler(plugins.Plugin):
return getattr(self.service, attr)
@classmethod
def get_plugin_choices(cls):
choices = super(ServiceHandler, cls).get_plugin_choices()
def get_choices(cls):
choices = super(ServiceHandler, cls).get_choices()
return [('', _("Default"))] + choices
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.utils.functional import cached_property
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.translations import ModelTranslation
from orchestra.core.validators import validate_name
from orchestra.models import queryset
from orchestra.utils.python import import_class
from . import settings
from .handlers import ServiceHandler
@ -20,6 +21,8 @@ from .handlers import ServiceHandler
autodiscover_modules('handlers')
rate_class = import_class(settings.SERVICES_RATE_CLASS)
class Service(models.Model):
NEVER = ''
@ -61,7 +64,7 @@ class Service(models.Model):
help_text=_("Handler used for processing this Service. A handler "
"enables customized behaviour far beyond what options "
"here allow to."),
choices=ServiceHandler.get_plugin_choices())
choices=ServiceHandler.get_choices())
is_active = models.BooleanField(_("active"), default=True)
ignore_superusers = models.BooleanField(_("ignore superusers"), default=True,
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)
rate_algorithm = models.CharField(_("rate algorithm"), max_length=16,
help_text=_("Algorithm used to interprete the rating table."),
# TODO this should be dynamic, retrieved from rate (plans) app
choices=(
('STEP_PRICE', _("Step price")),
('MATCH_PRICE', _("Match price")),
),
default='STEP_PRICE')
help_text=string_concat(_("Algorithm used to interprete the rating table."), *[
string_concat('<br>&nbsp;&nbsp;', method.verbose_name, ': ', method.help_text)
for name, method in rate_class.get_methods().iteritems()
]), choices=rate_class.get_choices(), default=rate_class.get_choices()[0][0])
on_cancel = models.CharField(_("on cancel"), max_length=16,
help_text=_("Defines the cancellation behaviour of this service."),
choices=(
@ -171,7 +171,7 @@ class Service(models.Model):
def handler(self):
""" Accessor of this service handler instance """
if self.handler_type:
return ServiceHandler.get_plugin(self.handler_type)(self)
return ServiceHandler.get(self.handler_type)(self)
return ServiceHandler(self)
def clean(self):
@ -231,8 +231,7 @@ class Service(models.Model):
@property
def rate_method(self):
rate_model = type(self).rates.related.model
return rate_model.get_methods()[self.rate_algorithm]
return rate_class.get_methods()[self.rate_algorithm]
def update_orders(self, commit=True):
order_model = get_model(settings.SERVICES_ORDER_MODEL)

View File

@ -9,13 +9,27 @@ SERVICES_SERVICE_TAXES = getattr(settings, 'SERVICES_SERVICE_TAXES', (
(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', (
@ -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',
('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', (
@ -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
else:
request = kwargs['request']
plugin = AppType.get_plugin(request.GET['type'])
plugin = AppType.get(request.GET['type'])
kwargs['choices'] = plugin.get_options_choices()
# Help text based on select widget
target = 'this.id.replace("name", "value")'

View File

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

View File

@ -1,9 +1,12 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from orchestra.settings import BASE_DOMAIN
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',
@ -17,16 +20,19 @@ WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH',
WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH',
# 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',
# 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_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',
'5.4-cgi')
'5.4-cgi'
)
WEBAPPS_PHP_CGI_BINARY_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_BINARY_PATH',
# 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',
# 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',
# 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',
# Server-side path where a under construction stock page is
# '/var/www/undercontruction/index.html',
'')
''
)
#WEBAPPS_TYPES_OVERRIDE = getattr(settings, 'WEBAPPS_TYPES_OVERRIDE', {})
#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',
'mysql.orchestra.lan')
'mysql.{}'.format(BASE_DOMAIN)
)

View File

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

View File

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

View File

@ -104,7 +104,7 @@ class WebsiteDirective(models.Model):
website = models.ForeignKey(Website, verbose_name=_("web site"),
related_name='directives')
name = models.CharField(_("name"), max_length=128,
choices=SiteDirective.get_plugin_choices())
choices=SiteDirective.get_choices())
value = models.CharField(_("value"), max_length=256)
def __unicode__(self):
@ -112,7 +112,7 @@ class WebsiteDirective(models.Model):
@cached_property
def directive_class(self):
return SiteDirective.get_plugin(self.name)
return SiteDirective.get(self.name)
@cached_property
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',
'%(user)s-%(site_name)s')
'%(user)s-%(site_name)s'
)
# 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")),
))
WEBSITES_DEFAULT_PROTOCOL = getattr(settings, 'WEBSITES_DEFAULT_PROTOCOL', 'http')
#WEBSITES_DEFAULT_PORT = getattr(settings, 'WEBSITES_DEFAULT_PORT', 80)
WEBSITES_DEFAULT_PROTOCOL = getattr(settings, 'WEBSITES_DEFAULT_PROTOCOL',
'http'
)
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', (
@ -47,23 +52,28 @@ WEBSITES_ENABLED_DIRECTIVES = getattr(settings, 'WEBSITES_ENABLED_DIRECTIVES', (
WEBSITES_BASE_APACHE_CONF = getattr(settings, 'WEBSITES_BASE_APACHE_CONF',
'/etc/apache2/')
'/etc/apache2/'
)
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',
'/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_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',

View File

@ -3,34 +3,40 @@ import os
from django.core.management.commands import makemessages
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):
""" Provides database translations support """
def handle(self, *args, **options):
do_database = os.getcwd() == get_site_root()
self.generated_database_files = []
if do_database:
self.project_locale_path = get_site_root()
self.generate_database_files()
super(Command, self).handle(*args, **options)
self.remove_database_files()
self.database_files = []
try:
if os.getcwd() == get_site_dir():
self.generate_database_files()
super(Command, self).handle(*args, **options)
finally:
self.remove_database_files()
def get_contents(self):
for model, fields in ModelTranslation._registry.iteritems():
contents = []
for field in fields:
contents = []
for content in model.objects.values_list('id', field):
pk, value = content
contents.append(
(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):
""" 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():
name = unicode(name)
maximum = None
@ -43,11 +49,11 @@ class Command(makemessages.Command):
for ix in xrange(maximum+1):
tmpcontent.append(content.get(ix, ''))
tmpcontent = u'\n'.join(tmpcontent) + '\n'
filepath = os.path.join(self.project_locale_path, 'database_%s.sql.py' % name)
self.generated_database_files.append(filepath)
with open(filepath, 'w') as tmpfile:
filename = 'database_%s.sql.py' % name
self.database_files.append(filename)
with open(filename, 'w') as tmpfile:
tmpfile.write(tmpcontent.encode('utf-8'))
def remove_database_files(self):
for path in self.generated_database_files:
for path in self.database_files:
os.unlink(path)

View File

@ -4,7 +4,7 @@ from optparse import make_option
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
@ -66,7 +66,7 @@ class Command(BaseCommand):
run('chmod +x %s' % 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 syncdb --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 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
@ -28,9 +28,9 @@ class Command(BaseCommand):
@check_root
def handle(self, *args, **options):
context = {
'site_root': get_site_root(),
'site_dir': get_site_dir(),
'username': options.get('username'),
'bin_path': path.join(get_orchestra_root(), 'bin'),
'bin_path': path.join(get_orchestra_dir(), 'bin'),
'processes': options.get('processes'),
}
@ -39,7 +39,7 @@ class Command(BaseCommand):
CELERYD_NODES="w1"
# Where to chdir at start.
CELERYD_CHDIR="%(site_root)s"
CELERYD_CHDIR="%(site_dir)s"
# How to call "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.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
@ -34,8 +34,8 @@ class Command(BaseCommand):
context = {
'project_name': get_project_name(),
'project_root': get_project_root(),
'site_root': get_site_root(),
'project_dir': get_project_dir(),
'site_dir': get_site_dir(),
'static_root': settings.STATIC_ROOT,
'user': options.get('user'),
'group': options.get('group') or options.get('user'),
@ -61,7 +61,7 @@ class Command(BaseCommand):
uwsgi_conf = (
'[uwsgi]\n'
'plugins = python\n'
'chdir = %(site_root)s\n'
'chdir = %(site_dir)s\n'
'module = %(project_name)s.wsgi\n'
'master = true\n'
'processes = %(processes)d\n'

View File

@ -3,7 +3,7 @@ from optparse import make_option
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
@ -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 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:
# Update existing settings_file

View File

@ -7,7 +7,7 @@ import sys
from django.core.management.base import BaseCommand
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
@ -93,7 +93,7 @@ class Command(BaseCommand):
def handle(self, *filenames, **options):
if not filenames:
filenames = [get_orchestra_root(), '.']
filenames = [get_orchestra_dir(), '.']
warnings = checkPaths(filenames)
for warning in warnings:
print warning

View File

@ -17,7 +17,7 @@ class SelectPluginAdminMixin(object):
plugin = getattr(obj, '%s_instance' % self.plugin_field)
self.form = getattr(plugin, 'get_change_form', plugin.get_form)()
else:
plugin = self.plugin.get_plugin(self.plugin_value)()
plugin = self.plugin.get(self.plugin_value)()
self.form = plugin.get_form()
return super(SelectPluginAdminMixin, self).get_form(request, obj, **kwargs)
@ -67,7 +67,7 @@ class SelectPluginAdminMixin(object):
self.plugin_value = plugin_value
if not plugin_value:
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 = {
'title': _("Add new %s") % plugin.verbose_name,
}

View File

@ -27,7 +27,7 @@ class Plugin(object):
return cls.plugins
@classmethod
def get_plugin(cls, name):
def get(cls, name):
if not hasattr(cls, '_registry'):
cls._registry = {
plugin.get_name(): plugin for plugin in cls.get_plugins()
@ -44,7 +44,7 @@ class Plugin(object):
return cls.get_name()
@classmethod
def get_plugin_choices(cls):
def get_choices(cls):
choices = []
for plugin in cls.get_plugins():
verbose = plugin.get_verbose_name()
@ -102,7 +102,7 @@ class PluginModelAdapter(Plugin):
return plugins
@classmethod
def get_plugin(cls, name):
def get(cls, name):
# don't cache, since models can change
for plugin in cls.get_plugins():
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
# 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',
_("%s Hosting Management" % SITE_NAME.capitalize()))
_("%s Hosting Management" % SITE_NAME.capitalize())
)
BASE_DOMAIN = getattr(settings, 'BASE_DOMAIN',
'orchestra.lan'
)
# Service management commands
START_SERVICES = getattr(settings, 'START_SERVICES',
['postgresql', 'celeryevcam', 'celeryd', 'celerybeat', ('uwsgi', 'nginx'),]
START_SERVICES = getattr(settings, 'START_SERVICES', [
'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',
'support@orchestra.lan'
'support@{}'.format(BASE_DOMAIN)
)

View File

@ -1,7 +1,7 @@
import os
def get_project_root():
def get_project_dir():
""" Return the current project path site/project """
from django.conf import settings
settings_file = os.sys.modules[settings.SETTINGS_MODULE].__file__
@ -10,15 +10,15 @@ def get_project_root():
def get_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 """
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 """
import orchestra
return os.path.dirname(os.path.realpath(orchestra.__file__))