Renamed mails apps to mailboxes

This commit is contained in:
Marc 2014-10-17 10:04:47 +00:00
parent 0dcdb4ba79
commit 1e8e57c979
38 changed files with 252 additions and 178 deletions

View File

@ -36,7 +36,7 @@ Django-orchestra can be installed on any Linux system, however it is **strongly
5. Create and configure a Postgres database 5. Create and configure a Postgres database
```bash ```bash
sudo python manage.py setuppostgres sudo python manage.py setuppostgres --db_password <password>
python manage.py syncdb python manage.py syncdb
python manage.py migrate python manage.py migrate
``` ```

View File

@ -136,3 +136,12 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* update_fields=[] doesn't trigger post save! * update_fields=[] doesn't trigger post save!
* lists -> SaaS ? * lists -> SaaS ?
* move bill contact to bills apps
* autocreate <account>.orchestra.lan
* Backend optimization
* fields = ()
* ignore_fields = ()
* based on a merge set of save(update_fields)

View File

@ -112,6 +112,7 @@ class ChangeAddFieldsMixin(object):
add_fieldsets = () add_fieldsets = ()
add_form = None add_form = None
change_readonly_fields = () change_readonly_fields = ()
add_inlines = ()
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj=obj) fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj=obj)
@ -129,9 +130,10 @@ class ChangeAddFieldsMixin(object):
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):
""" add_inlines and inline.parent_object """ """ add_inlines and inline.parent_object """
self.inlines = getattr(self, 'add_inlines', self.inlines)
if obj: if obj:
self.inlines = type(self).inlines self.inlines = type(self).inlines
else:
self.inlines = self.add_inlines or self.inlines
inlines = super(ChangeAddFieldsMixin, self).get_inline_instances(request, obj=obj) inlines = super(ChangeAddFieldsMixin, self).get_inline_instances(request, obj=obj)
for inline in inlines: for inline in inlines:
inline.parent_object = obj inline.parent_object = obj

View File

@ -7,13 +7,14 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import admin_date from orchestra.admin.utils import admin_date, insertattr
from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin, AccountAdmin
from orchestra.forms.widgets import paddingCheckboxSelectMultiple
from . import settings from . import settings
from .actions import download_bills, view_bill, close_bills, send_bills, validate_contact from .actions import download_bills, view_bill, close_bills, send_bills, validate_contact
from .filters import BillTypeListFilter from .filters import BillTypeListFilter, HasBillContactListFilter
from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine, BillContact
PAYMENT_STATE_COLORS = { PAYMENT_STATE_COLORS = {
@ -164,3 +165,27 @@ admin.site.register(AmendmentInvoice, BillAdmin)
admin.site.register(Fee, BillAdmin) admin.site.register(Fee, BillAdmin)
admin.site.register(AmendmentFee, BillAdmin) admin.site.register(AmendmentFee, BillAdmin)
admin.site.register(ProForma, BillAdmin) admin.site.register(ProForma, BillAdmin)
class BillContactInline(admin.StackedInline):
model = BillContact
fields = ('name', 'address', ('city', 'zipcode'), 'country', 'vat')
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
if db_field.name == 'address':
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
if db_field.name == 'email_usage':
kwargs['widget'] = paddingCheckboxSelectMultiple(45)
return super(BillContactInline, self).formfield_for_dbfield(db_field, **kwargs)
def has_bill_contact(account):
return hasattr(account, 'billcontact')
has_bill_contact.boolean = True
has_bill_contact.admin_order_field = 'billcontact'
insertattr(AccountAdmin, 'inlines', BillContactInline)
insertattr(AccountAdmin, 'list_display', has_bill_contact)
insertattr(AccountAdmin, 'list_filter', HasBillContactListFilter)

View File

@ -10,6 +10,3 @@ from .serializers import BillSerializer
class BillViewSet(AccountApiMixin, viewsets.ModelViewSet): class BillViewSet(AccountApiMixin, viewsets.ModelViewSet):
model = Bill model = Bill
serializer_class = BillSerializer serializer_class = BillSerializer
router.register(r'bills', BillViewSet)

View File

@ -22,7 +22,6 @@ class BillTypeListFilter(SimpleListFilter):
('proforma', _("Pro-forma")), ('proforma', _("Pro-forma")),
) )
def queryset(self, request, queryset): def queryset(self, request, queryset):
return queryset return queryset
@ -37,3 +36,20 @@ class BillTypeListFilter(SimpleListFilter):
'display': title, 'display': title,
} }
class HasBillContactListFilter(SimpleListFilter):
""" Filter Nodes by group according to request.user """
title = _("has bill contact")
parameter_name = 'bill'
def lookups(self, request, model_admin):
return (
('True', _("Yes")),
('False', _("No")),
)
def queryset(self, request, queryset):
if self.value() == 'True':
return queryset.filter(billcontact__isnull=False)
if self.value() == 'False':
return queryset.filter(billcontact__isnull=True)

View File

@ -16,6 +16,22 @@ from orchestra.utils.html import html_to_pdf
from . import settings from . import settings
class BillContact(models.Model):
account = models.OneToOneField('accounts.Account', verbose_name=_("account"),
related_name='billcontact')
name = models.CharField(_("name"), max_length=256)
address = models.TextField(_("address"))
city = models.CharField(_("city"), max_length=128,
default=settings.BILLS_CONTACT_DEFAULT_CITY)
zipcode = models.PositiveIntegerField(_("zip code"))
country = models.CharField(_("country"), max_length=20,
default=settings.BILLS_CONTACT_DEFAULT_COUNTRY)
vat = models.CharField(_("VAT number"), max_length=64)
def __unicode__(self):
return self.name
class BillManager(models.Manager): class BillManager(models.Manager):
def get_queryset(self): def get_queryset(self):
queryset = super(BillManager, self).get_queryset() queryset = super(BillManager, self).get_queryset()
@ -73,11 +89,11 @@ class Bill(models.Model):
@cached_property @cached_property
def seller(self): def seller(self):
return Account.get_main().invoicecontact return Account.get_main().billcontact
@cached_property @cached_property
def buyer(self): def buyer(self):
return self.account.invoicecontact return self.account.billcontact
@cached_property @cached_property
def payment_state(self): def payment_state(self):

View File

@ -1,8 +1,10 @@
from rest_framework import serializers from rest_framework import serializers
from orchestra.api import router
from orchestra.apps.accounts.models import Account
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from .models import Bill, BillLine from .models import Bill, BillLine, BillContact
class BillLineSerializer(serializers.HyperlinkedModelSerializer): class BillLineSerializer(serializers.HyperlinkedModelSerializer):
@ -20,3 +22,12 @@ class BillSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeriali
'url', 'number', 'type', 'total', 'is_sent', 'created_on', 'due_on', 'url', 'number', 'type', 'total', 'is_sent', 'created_on', 'due_on',
'comments', 'html', 'lines' 'comments', 'html', 'lines'
) )
class BillContactSerializer(AccountSerializerMixin, serializers.ModelSerializer):
class Meta:
model = BillContact
fields = ('name', 'address', 'city', 'zipcode', 'country', 'vat')
router.insert(Account, 'billcontact', BillContactSerializer, required=False)

View File

@ -3,35 +3,48 @@ from django.conf import settings
BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4) BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4)
BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX', 'I') BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX', 'I')
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A') BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A')
BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX', 'F') BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX', 'F')
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBER_PREFIX', 'B') 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_PROFORMA_NUMBER_PREFIX = getattr(settings, 'BILLS_PROFORMA_NUMBER_PREFIX', 'P')
BILLS_DEFAULT_TEMPLATE = getattr(settings, 'BILLS_DEFAULT_TEMPLATE', 'bills/microspective.html') BILLS_DEFAULT_TEMPLATE = getattr(settings, 'BILLS_DEFAULT_TEMPLATE',
'bills/microspective.html')
BILLS_FEE_TEMPLATE = getattr(settings, 'BILLS_FEE_TEMPLATE', 'bills/microspective-fee.html')
BILLS_PROFORMA_TEMPLATE = getattr(settings, 'BILLS_PROFORMA_TEMPLATE', 'bills/microspective-proforma.html') BILLS_FEE_TEMPLATE = getattr(settings, 'BILLS_FEE_TEMPLATE',
'bills/microspective-fee.html')
BILLS_PROFORMA_TEMPLATE = getattr(settings, 'BILLS_PROFORMA_TEMPLATE',
'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@orchestra.lan')
BILLS_SELLER_WEBSITE = getattr(settings, 'BILLS_SELLER_WEBSITE', 'www.orchestra.lan') BILLS_SELLER_WEBSITE = getattr(settings, 'BILLS_SELLER_WEBSITE', 'www.orchestra.lan')
BILLS_SELLER_BANK_ACCOUNT = getattr(settings, 'BILLS_SELLER_BANK_ACCOUNT', '0000 0000 00 00000000 (Orchestra Bank)')
BILLS_SELLER_BANK_ACCOUNT = getattr(settings, 'BILLS_SELLER_BANK_ACCOUNT',
'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',
@ -39,3 +52,9 @@ BILLS_EMAIL_NOTIFICATION_TEMPLATE = getattr(settings, 'BILLS_EMAIL_NOTIFICATION_
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_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY', 'Spain')

View File

@ -141,6 +141,3 @@
</div> </div>
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View File

@ -6,8 +6,8 @@ from orchestra.admin import AtLeastOneRequiredInlineFormSet
from orchestra.admin.utils import insertattr from orchestra.admin.utils import insertattr
from orchestra.apps.accounts.admin import AccountAdmin, AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdmin, AccountAdminMixin
from orchestra.forms.widgets import paddingCheckboxSelectMultiple from orchestra.forms.widgets import paddingCheckboxSelectMultiple
from .filters import HasInvoiceContactListFilter
from .models import Contact, InvoiceContact from .models import Contact
class ContactAdmin(AccountAdminMixin, admin.ModelAdmin): class ContactAdmin(AccountAdminMixin, admin.ModelAdmin):
@ -69,20 +69,7 @@ class ContactAdmin(AccountAdminMixin, admin.ModelAdmin):
admin.site.register(Contact, ContactAdmin) admin.site.register(Contact, ContactAdmin)
class InvoiceContactInline(admin.StackedInline): class ContactInline(admin.StackedInline):
model = InvoiceContact
fields = ('name', 'address', ('city', 'zipcode'), 'country', 'vat')
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
if db_field.name == 'address':
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
if db_field.name == 'email_usage':
kwargs['widget'] = paddingCheckboxSelectMultiple(45)
return super(InvoiceContactInline, self).formfield_for_dbfield(db_field, **kwargs)
class ContactInline(InvoiceContactInline):
model = Contact model = Contact
formset = AtLeastOneRequiredInlineFormSet formset = AtLeastOneRequiredInlineFormSet
extra = 0 extra = 0
@ -94,17 +81,16 @@ class ContactInline(InvoiceContactInline):
def get_extra(self, request, obj=None, **kwargs): def get_extra(self, request, obj=None, **kwargs):
return 0 if obj and obj.contacts.exists() else 1 return 0 if obj and obj.contacts.exists() else 1
def formfield_for_dbfield(self, db_field, **kwargs):
def has_invoice(account): """ Make value input widget bigger """
return hasattr(account, 'invoicecontact') if db_field.name == 'address':
has_invoice.boolean = True kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
has_invoice.admin_order_field = 'invoicecontact' if db_field.name == 'email_usage':
kwargs['widget'] = paddingCheckboxSelectMultiple(45)
return super(ContactInline, self).formfield_for_dbfield(db_field, **kwargs)
insertattr(AccountAdmin, 'inlines', ContactInline) insertattr(AccountAdmin, 'inlines', ContactInline)
insertattr(AccountAdmin, 'inlines', InvoiceContactInline)
insertattr(AccountAdmin, 'list_display', has_invoice)
insertattr(AccountAdmin, 'list_filter', HasInvoiceContactListFilter)
search_fields = ( search_fields = (
'contacts__short_name', 'contacts__full_name', 'contacts__phone', 'contacts__short_name', 'contacts__full_name', 'contacts__phone',
'contacts__phone2', 'contacts__email' 'contacts__phone2', 'contacts__email'

View File

@ -3,8 +3,8 @@ from rest_framework import viewsets
from orchestra.api import router from orchestra.api import router
from orchestra.apps.accounts.api import AccountApiMixin from orchestra.apps.accounts.api import AccountApiMixin
from .models import Contact, InvoiceContact from .models import Contact
from .serializers import ContactSerializer, InvoiceContactSerializer from .serializers import ContactSerializer
class ContactViewSet(AccountApiMixin, viewsets.ModelViewSet): class ContactViewSet(AccountApiMixin, viewsets.ModelViewSet):
@ -12,10 +12,5 @@ class ContactViewSet(AccountApiMixin, viewsets.ModelViewSet):
serializer_class = ContactSerializer serializer_class = ContactSerializer
class InvoiceContactViewSet(AccountApiMixin, viewsets.ModelViewSet):
model = InvoiceContact
serializer_class = InvoiceContactSerializer
router.register(r'contacts', ContactViewSet) router.register(r'contacts', ContactViewSet)
router.register(r'invoicecontacts', InvoiceContactViewSet)

View File

@ -1,20 +0,0 @@
from django.contrib.admin import SimpleListFilter
from django.utils.translation import ugettext_lazy as _
class HasInvoiceContactListFilter(SimpleListFilter):
""" Filter Nodes by group according to request.user """
title = _("has invoice contact")
parameter_name = 'invoice'
def lookups(self, request, model_admin):
return (
('True', _("Yes")),
('False', _("No")),
)
def queryset(self, request, queryset):
if self.value() == 'True':
return queryset.filter(invoicecontact__isnull=False)
if self.value() == 'False':
return queryset.filter(invoicecontact__isnull=True)

View File

@ -50,20 +50,4 @@ class Contact(models.Model):
return self.short_name return self.short_name
class InvoiceContact(models.Model):
account = models.OneToOneField('accounts.Account', verbose_name=_("account"),
related_name='invoicecontact')
name = models.CharField(_("name"), max_length=256)
address = models.TextField(_("address"))
city = models.CharField(_("city"), max_length=128,
default=settings.CONTACTS_DEFAULT_CITY)
zipcode = models.PositiveIntegerField(_("zip code"))
country = models.CharField(_("country"), max_length=20,
default=settings.CONTACTS_DEFAULT_COUNTRY)
vat = models.CharField(_("VAT number"), max_length=64)
def __unicode__(self):
return self.name
accounts.register(Contact) accounts.register(Contact)

View File

@ -3,7 +3,7 @@ from rest_framework import serializers
from orchestra.api.serializers import MultiSelectField from orchestra.api.serializers import MultiSelectField
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from .models import Contact, InvoiceContact from .models import Contact
class ContactSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class ContactSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
@ -14,9 +14,3 @@ class ContactSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
'url', 'short_name', 'full_name', 'email', 'email_usage', 'phone', 'url', 'short_name', 'full_name', 'email', 'email_usage', 'phone',
'phone2', 'address', 'city', 'zipcode', 'country' 'phone2', 'address', 'city', 'zipcode', 'country'
) )
class InvoiceContactSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class Meta:
model = InvoiceContact
fields = ('url', 'name', 'address', 'city', 'zipcode', 'country', 'vat')

View File

@ -13,6 +13,8 @@ class MySQLBackend(ServiceController):
model = 'databases.Database' model = 'databases.Database'
def save(self, database): def save(self, database):
if database.type != database.MYSQL:
return
context = self.get_context(database) context = self.get_context(database)
# Not available on delete() # Not available on delete()
context['owner'] = database.owner context['owner'] = database.owner
@ -32,6 +34,8 @@ class MySQLBackend(ServiceController):
)) ))
def delete(self, database): def delete(self, database):
if database.type != database.MYSQL:
return
context = self.get_context(database) context = self.get_context(database)
self.append("mysql -e 'DROP DATABASE `%(database)s`;'" % context) self.append("mysql -e 'DROP DATABASE `%(database)s`;'" % context)
@ -50,6 +54,8 @@ class MySQLUserBackend(ServiceController):
model = 'databases.DatabaseUser' model = 'databases.DatabaseUser'
def save(self, user): def save(self, user):
if user.type != user.MYSQL:
return
context = self.get_context(user) context = self.get_context(user)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'CREATE USER "%(username)s"@"%(host)s";' || true \ mysql -e 'CREATE USER "%(username)s"@"%(host)s";' || true \
@ -61,6 +67,8 @@ class MySQLUserBackend(ServiceController):
)) ))
def delete(self, user): def delete(self, user):
if user.type != user.MYSQL:
return
context = self.get_context(user) context = self.get_context(user)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'DROP USER "%(username)s"@"%(host)s";' \ mysql -e 'DROP USER "%(username)s"@"%(host)s";' \
@ -83,6 +91,8 @@ class MysqlDisk(ServiceMonitor):
verbose_name = _("MySQL disk") verbose_name = _("MySQL disk")
def exceeded(self, db): def exceeded(self, db):
if db.type != db.MYSQL:
return
context = self.get_context(db) context = self.get_context(db)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'UPDATE db SET Insert_priv="N", Create_priv="N" WHERE Db="%(db_name)s";' \ mysql -e 'UPDATE db SET Insert_priv="N", Create_priv="N" WHERE Db="%(db_name)s";' \
@ -90,6 +100,8 @@ class MysqlDisk(ServiceMonitor):
)) ))
def recovery(self, db): def recovery(self, db):
if db.type != db.MYSQL:
return
context = self.get_context(db) context = self.get_context(db)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'UPDATE db SET Insert_priv="Y", Create_priv="Y" WHERE Db="%(db_name)s";' \ mysql -e 'UPDATE db SET Insert_priv="Y", Create_priv="Y" WHERE Db="%(db_name)s";' \
@ -97,6 +109,8 @@ class MysqlDisk(ServiceMonitor):
)) ))
def monitor(self, db): def monitor(self, db):
if db.type != db.MYSQL:
return
context = self.get_context(db) context = self.get_context(db)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
echo %(db_id)s $(mysql -B -e '" echo %(db_id)s $(mysql -B -e '"

View File

@ -41,8 +41,8 @@ Database.users.through._meta.unique_together = (('database', 'databaseuser'),)
class DatabaseUser(models.Model): class DatabaseUser(models.Model):
MYSQL = 'mysql' MYSQL = Database.MYSQL
POSTGRESQL = 'postgresql' POSTGRESQL = Database.POSTGRESQL
username = models.CharField(_("username"), max_length=16, # MySQL usernames 16 char long username = models.CharField(_("username"), max_length=16, # MySQL usernames 16 char long
validators=[validators.validate_name]) validators=[validators.validate_name])

View File

@ -76,7 +76,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, AccountAdminMixin, ExtendedModelAdm
def addresses_field(self, mailbox): def addresses_field(self, mailbox):
""" Address form field with "Add address" button """ """ Address form field with "Add address" button """
account = mailbox.account account = mailbox.account
add_url = reverse('admin:mails_address_add') add_url = reverse('admin:mailboxes_address_add')
add_url += '?account=%d&mailboxes=%s' % (account.pk, mailbox.pk) add_url += '?account=%d&mailboxes=%s' % (account.pk, mailbox.pk)
img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">' img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">'
onclick = 'onclick="return showAddAnotherPopup(this);"' onclick = 'onclick="return showAddAnotherPopup(this);"'
@ -84,7 +84,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, AccountAdminMixin, ExtendedModelAdm
add_url=add_url, onclick=onclick, img=img) add_url=add_url, onclick=onclick, img=img)
value = '%s<br><br>' % add_link value = '%s<br><br>' % add_link
for pk, name, domain in mailbox.addresses.values_list('pk', 'name', 'domain__name'): for pk, name, domain in mailbox.addresses.values_list('pk', 'name', 'domain__name'):
url = reverse('admin:mails_address_change', args=(pk,)) url = reverse('admin:mailboxes_address_change', args=(pk,))
name = '%s@%s' % (name, domain) name = '%s@%s' % (name, domain)
value += '<li><a href="%s">%s</a></li>' % (url, name) value += '<li><a href="%s">%s</a></li>' % (url, name)
value = '<ul>%s</ul>' % value value = '<ul>%s</ul>' % value

View File

@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
class PasswdVirtualUserBackend(ServiceController): class PasswdVirtualUserBackend(ServiceController):
verbose_name = _("Mail virtual user (passwd-file)") verbose_name = _("Mail virtual user (passwd-file)")
model = 'mails.Mailbox' model = 'mailboxes.Mailbox'
# TODO related_models = ('resources__content_type') ?? needed for updating disk usage from resource.data # TODO related_models = ('resources__content_type') ?? needed for updating disk usage from resource.data
DEFAULT_GROUP = 'postfix' DEFAULT_GROUP = 'postfix'
@ -86,7 +86,7 @@ class PasswdVirtualUserBackend(ServiceController):
def commit(self): def commit(self):
context = { context = {
'virtual_mailbox_maps': settings.MAILS_VIRTUAL_MAILBOX_MAPS_PATH 'virtual_mailbox_maps': settings.MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH
} }
self.append( self.append(
"[[ $UPDATED_VIRTUAL_MAILBOX_MAPS == 1 ]] && { postmap %(virtual_mailbox_maps)s; }" "[[ $UPDATED_VIRTUAL_MAILBOX_MAPS == 1 ]] && { postmap %(virtual_mailbox_maps)s; }"
@ -102,11 +102,11 @@ class PasswdVirtualUserBackend(ServiceController):
'gid': 10000 + mailbox.pk, 'gid': 10000 + mailbox.pk,
'group': self.DEFAULT_GROUP, 'group': self.DEFAULT_GROUP,
'quota': self.get_quota(mailbox), 'quota': self.get_quota(mailbox),
'passwd_path': settings.MAILS_PASSWD_PATH, 'passwd_path': settings.MAILBOXES_PASSWD_PATH,
'home': mailbox.get_home(), 'home': mailbox.get_home(),
'banner': self.get_banner(), 'banner': self.get_banner(),
'virtual_mailbox_maps': settings.MAILS_VIRTUAL_MAILBOX_MAPS_PATH, 'virtual_mailbox_maps': settings.MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH,
'mailbox_domain': settings.MAILS_VIRTUAL_MAILBOX_DEFAULT_DOMAIN, 'mailbox_domain': settings.MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN,
} }
context['extra_fields'] = self.get_extra_fields(mailbox, context) context['extra_fields'] = self.get_extra_fields(mailbox, context)
context['passwd'] = '{username}:{password}:{uid}:{gid}::{home}::{extra_fields}'.format(**context) context['passwd'] = '{username}:{password}:{uid}:{gid}::{home}::{extra_fields}'.format(**context)
@ -115,7 +115,7 @@ class PasswdVirtualUserBackend(ServiceController):
class PostfixAddressBackend(ServiceController): class PostfixAddressBackend(ServiceController):
verbose_name = _("Postfix address") verbose_name = _("Postfix address")
model = 'mails.Address' model = 'mailboxes.Address'
def include_virtual_alias_domain(self, context): def include_virtual_alias_domain(self, context):
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
@ -185,8 +185,8 @@ class PostfixAddressBackend(ServiceController):
def get_context_files(self): def get_context_files(self):
return { return {
'virtual_alias_domains': settings.MAILS_VIRTUAL_ALIAS_DOMAINS_PATH, 'virtual_alias_domains': settings.MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH,
'virtual_alias_maps': settings.MAILS_VIRTUAL_ALIAS_MAPS_PATH 'virtual_alias_maps': settings.MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH
} }
def get_context(self, address): def get_context(self, address):
@ -194,7 +194,7 @@ class PostfixAddressBackend(ServiceController):
context.update({ context.update({
'domain': address.domain, 'domain': address.domain,
'email': address.email, 'email': address.email,
'mailbox_domain': settings.MAILS_VIRTUAL_MAILBOX_DEFAULT_DOMAIN, 'mailbox_domain': settings.MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN,
}) })
return context return context
@ -205,7 +205,7 @@ class AutoresponseBackend(ServiceController):
class MaildirDisk(ServiceMonitor): class MaildirDisk(ServiceMonitor):
model = 'mails.Mailbox' model = 'mailboxes.Mailbox'
resource = ServiceMonitor.DISK resource = ServiceMonitor.DISK
verbose_name = _("Maildir disk usage") verbose_name = _("Maildir disk usage")

View File

@ -22,14 +22,14 @@ class Mailbox(models.Model):
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='mailboxes') related_name='mailboxes')
filtering = models.CharField(max_length=16, filtering = models.CharField(max_length=16,
choices=[(k, v[0]) for k,v in settings.MAILS_MAILBOX_FILTERINGS.iteritems()], choices=[(k, v[0]) for k,v in settings.MAILBOXES_MAILBOX_FILTERINGS.iteritems()],
default=settings.MAILS_MAILBOX_DEFAULT_FILTERING) default=settings.MAILBOXES_MAILBOX_DEFAULT_FILTERING)
custom_filtering = models.TextField(_("filtering"), blank=True, custom_filtering = models.TextField(_("filtering"), blank=True,
validators=[validators.validate_sieve], validators=[validators.validate_sieve],
help_text=_("Arbitrary email filtering in sieve language. " help_text=_("Arbitrary email filtering in sieve language. "
"This overrides any automatic junk email filtering")) "This overrides any automatic junk email filtering"))
is_active = models.BooleanField(_("active"), default=True) is_active = models.BooleanField(_("active"), default=True)
# addresses = models.ManyToManyField('mails.Address', # addresses = models.ManyToManyField('mailboxes.Address',
# verbose_name=_("addresses"), # verbose_name=_("addresses"),
# related_name='mailboxes', blank=True) # related_name='mailboxes', blank=True)
@ -54,7 +54,7 @@ class Mailbox(models.Model):
'name': self.name, 'name': self.name,
'username': self.name, 'username': self.name,
} }
home = settings.MAILS_HOME % context home = settings.MAILBOXES_HOME % context
return home.rstrip('/') return home.rstrip('/')
def clean(self): def clean(self):
@ -62,7 +62,7 @@ class Mailbox(models.Model):
self.custom_filtering = '' self.custom_filtering = ''
def get_filtering(self): def get_filtering(self):
__, filtering = settings.MAILS_MAILBOX_FILTERINGS[self.filtering] __, filtering = settings.MAILBOXES_MAILBOX_FILTERINGS[self.filtering]
if isinstance(filtering, basestring): if isinstance(filtering, basestring):
return filtering return filtering
return filtering(self) return filtering(self)
@ -83,7 +83,7 @@ class Mailbox(models.Model):
class Address(models.Model): class Address(models.Model):
name = models.CharField(_("name"), max_length=64, name = models.CharField(_("name"), max_length=64,
validators=[validators.validate_emailname]) validators=[validators.validate_emailname])
domain = models.ForeignKey(settings.MAILS_DOMAIN_MODEL, domain = models.ForeignKey(settings.MAILBOXES_DOMAIN_MODEL,
verbose_name=_("domain"), verbose_name=_("domain"),
related_name='addresses') related_name='addresses')
mailboxes = models.ManyToManyField(Mailbox, mailboxes = models.ManyToManyField(Mailbox,

View File

@ -10,10 +10,33 @@ from orchestra.core.validators import validate_password
from .models import Mailbox, Address from .models import Mailbox, Address
class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class Meta:
model = Address.domain.field.rel.to
fields = ('url', 'name')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class RelatedAddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
domain = RelatedDomainSerializer()
class Meta:
model = Address
fields = ('url', 'name', 'domain', 'forward')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class MailboxSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): class MailboxSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'), password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, required=False, validators=[validate_password], write_only=True, required=False,
widget=widgets.PasswordInput) widget=widgets.PasswordInput)
addresses = RelatedAddressSerializer(many=True, read_only=True)
class Meta: class Meta:
model = Mailbox model = Mailbox
@ -48,16 +71,6 @@ class RelatedMailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedMo
return get_object_or_404(queryset, name=data['name']) return get_object_or_404(queryset, name=data['name'])
class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class Meta:
model = Address.domain.field.rel.to
fields = ('url', 'name')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
domain = RelatedDomainSerializer() domain = RelatedDomainSerializer()
mailboxes = RelatedMailboxSerializer(many=True, allow_add_remove=True, required=False) mailboxes = RelatedMailboxSerializer(many=True, allow_add_remove=True, required=False)

View File

@ -4,40 +4,39 @@ from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
MAILS_DOMAIN_MODEL = getattr(settings, 'MAILS_DOMAIN_MODEL', 'domains.Domain') MAILBOXES_DOMAIN_MODEL = getattr(settings, 'MAILBOXES_DOMAIN_MODEL', 'domains.Domain')
MAILS_HOME = getattr(settings, 'MAILS_HOME', '/home/%(name)s/') MAILBOXES_HOME = getattr(settings, 'MAILBOXES_HOME', '/home/%(name)s/')
MAILS_SIEVETEST_PATH = getattr(settings, 'MAILS_SIEVETEST_PATH', '/dev/shm') MAILBOXES_SIEVETEST_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_PATH', '/dev/shm')
MAILS_SIEVETEST_BIN_PATH = getattr(settings, 'MAILS_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')
MAILS_VIRTUAL_MAILBOX_MAPS_PATH = getattr(settings, 'MAILS_VIRTUAL_MAILBOX_MAPS_PATH', MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH',
'/etc/postfix/virtual_mailboxes') '/etc/postfix/virtual_mailboxes')
MAILS_VIRTUAL_ALIAS_MAPS_PATH = getattr(settings, 'MAILS_VIRTUAL_ALIAS_MAPS_PATH', MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH',
'/etc/postfix/virtual_aliases') '/etc/postfix/virtual_aliases')
MAILS_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'MAILS_VIRTUAL_ALIAS_DOMAINS_PATH', MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH',
'/etc/postfix/virtual_domains') '/etc/postfix/virtual_domains')
MAILS_VIRTUAL_MAILBOX_DEFAULT_DOMAIN = getattr(settings, 'MAILS_VIRTUAL_MAILBOX_DEFAULT_DOMAIN', MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN = getattr(settings, 'MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN',
'orchestra.lan') 'orchestra.lan')
MAILS_PASSWD_PATH = getattr(settings, 'MAILS_PASSWD_PATH', MAILBOXES_PASSWD_PATH = getattr(settings, 'MAILBOXES_PASSWD_PATH',
'/etc/dovecot/passwd') '/etc/dovecot/passwd')
MAILBOXES_MAILBOX_FILTERINGS = getattr(settings, 'MAILBOXES_MAILBOX_FILTERINGS', {
MAILS_MAILBOX_FILTERINGS = getattr(settings, 'MAILS_MAILBOX_FILTERINGS', {
# value: (verbose_name, filter) # value: (verbose_name, filter)
'DISABLE': (_("Disable"), ''), 'DISABLE': (_("Disable"), ''),
'REJECT': (_("Reject spam"), textwrap.dedent(""" 'REJECT': (_("Reject spam"), textwrap.dedent("""
@ -56,4 +55,4 @@ MAILS_MAILBOX_FILTERINGS = getattr(settings, 'MAILS_MAILBOX_FILTERINGS', {
}) })
MAILS_MAILBOX_DEFAULT_FILTERING = getattr(settings, 'MAILS_MAILBOX_DEFAULT_FILTERING', 'REDIRECT') MAILBOXES_MAILBOX_DEFAULT_FILTERING = getattr(settings, 'MAILBOXES_MAILBOX_DEFAULT_FILTERING', 'REDIRECT')

View File

@ -228,7 +228,7 @@ class MailboxMixin(object):
imap.create(folder) imap.create(folder)
self.validate_mailbox(username) self.validate_mailbox(username)
token = random_ascii(100) token = random_ascii(100)
self.send_email("%s@%s" % (username, settings.MAILS_VIRTUAL_MAILBOX_DEFAULT_DOMAIN), token) self.send_email("%s@%s" % (username, settings.MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN), token)
home = Mailbox.objects.get(name=username).get_home() home = Mailbox.objects.get(name=username).get_home()
sshrun(self.MASTER_SERVER, sshrun(self.MASTER_SERVER,
"grep '%s' %s/Maildir/.%s/new/*" % (token, home, folder), display=False) "grep '%s' %s/Maildir/.%s/new/*" % (token, home, folder), display=False)
@ -300,7 +300,7 @@ class AdminMailboxMixin(MailboxMixin):
@snapshot_on_error @snapshot_on_error
def add(self, username, password, quota=None, filtering=None): def add(self, username, password, quota=None, filtering=None):
url = self.live_server_url + reverse('admin:mails_mailbox_add') url = self.live_server_url + reverse('admin:mailboxes_mailbox_add')
self.selenium.get(url) self.selenium.get(url)
account_input = self.selenium.find_element_by_id('id_account') account_input = self.selenium.find_element_by_id('id_account')
@ -346,7 +346,7 @@ class AdminMailboxMixin(MailboxMixin):
@snapshot_on_error @snapshot_on_error
def add_address(self, username, name, domain): def add_address(self, username, name, domain):
url = self.live_server_url + reverse('admin:mails_address_add') url = self.live_server_url + reverse('admin:mailboxes_address_add')
self.selenium.get(url) self.selenium.get(url)
name_field = self.selenium.find_element_by_id('id_name') name_field = self.selenium.find_element_by_id('id_name')

View File

@ -40,13 +40,13 @@ def validate_forward(value):
def validate_sieve(value): def validate_sieve(value):
sieve_name = '%s.sieve' % hashlib.md5(value).hexdigest() sieve_name = '%s.sieve' % hashlib.md5(value).hexdigest()
path = os.path.join(settings.MAILS_SIEVETEST_PATH, sieve_name) path = os.path.join(settings.MAILBOXES_SIEVETEST_PATH, sieve_name)
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_root()
} }
sievetest = settings.MAILS_SIEVETEST_BIN_PATH % context sievetest = settings.MAILBOXES_SIEVETEST_BIN_PATH % context
try: try:
test = run(' '.join([sievetest, path, '/dev/null']), display=False) test = run(' '.join([sievetest, path, '/dev/null']), display=False)
except CommandError: except CommandError:

View File

@ -19,18 +19,22 @@ transports = {}
def BashSSH(backend, log, server, cmds): def BashSSH(backend, log, server, cmds):
from .models import BackendLog from .models import BackendLog
# TODO save remote file into a root read only directory to avoid users sniffing passwords and stuff
script = '\n'.join(['set -e', 'set -o pipefail'] + cmds + ['exit 0']) script = '\n'.join(['set -e', 'set -o pipefail'] + cmds + ['exit 0'])
script = script.replace('\r', '') script = script.replace('\r', '')
log.script = script digest = hashlib.md5(script).hexdigest()
path = os.path.join(settings.ORCHESTRATION_TEMP_SCRIPT_PATH, digest)
remote_path = "%s.remote" % path
log.script = '# %s\n%s' % (remote_path, script)
log.save(update_fields=['script']) log.save(update_fields=['script'])
logger.debug('%s is going to be executed on %s' % (backend, server))
channel = None channel = None
ssh = None ssh = None
try: try:
logger.debug('%s is going to be executed on %s' % (backend, server))
# Avoid "Argument list too long" on large scripts by genereting a file # Avoid "Argument list too long" on large scripts by genereting a file
# and scping it to the remote server # and scping it to the remote server
digest = hashlib.md5(script).hexdigest()
path = os.path.join(settings.ORCHESTRATION_TEMP_SCRIPT_PATH, digest)
with open(path, 'w') as script_file: with open(path, 'w') as script_file:
script_file.write(script) script_file.write(script)
@ -50,19 +54,19 @@ def BashSSH(backend, log, server, cmds):
# Copy script to remote server # Copy script to remote server
sftp = paramiko.SFTPClient.from_transport(transport) sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(path, "%s.remote" % path) sftp.put(path, remote_path)
sftp.close() sftp.close()
os.remove(path) os.remove(path)
# Execute it # Execute it
context = { context = {
'path': "%s.remote" % path, 'remote_path': remote_path,
'digest': digest 'digest': digest
} }
cmd = ( cmd = (
"[[ $(md5sum %(path)s|awk {'print $1'}) == %(digest)s ]] && bash %(path)s\n" "[[ $(md5sum %(remote_path)s|awk {'print $1'}) == %(digest)s ]] && bash %(remote_path)s\n"
"RETURN_CODE=$?\n" "RETURN_CODE=$?\n"
# TODO "rm -fr %(path)s\n" # TODO "rm -fr %(remote_path)s\n"
"exit $RETURN_CODE" % context "exit $RETURN_CODE" % context
) )
channel = transport.open_session() channel = transport.open_session()

View File

@ -1,17 +1,16 @@
import decimal import decimal
from django.contrib.contenttypes.models import ContentType
from django.core.validators import ValidationError
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
from django.db.models.loading import get_model from django.db.models.loading import get_model
from django.contrib.contenttypes.models import ContentType
from django.core.validators import ValidationError
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 ugettext_lazy as _
from orchestra.core import caches, services, accounts from orchestra.core import caches, services, accounts
from orchestra.models import queryset from orchestra.models import queryset
#from orchestra.utils.apps import autodiscover
from . import settings, rating from . import settings, rating
from .handlers import ServiceHandler from .handlers import ServiceHandler
@ -187,8 +186,6 @@ class Service(models.Model):
cache.set(key, services) cache.set(key, services)
return services return services
# FIXME some times caching is nasty, do we really have to? make get_plugin more efficient?
# @property
@cached_property @cached_property
def handler(self): def handler(self):
""" Accessor of this service handler instance """ """ Accessor of this service handler instance """

View File

@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.utils import timezone from django.utils import timezone
from freezegun import freeze_time from freezegun import freeze_time
from orchestra.apps.mails.models import Mailbox from orchestra.apps.mailboxes.models import Mailbox
from orchestra.apps.resources.models import Resource, ResourceData from orchestra.apps.resources.models import Resource, ResourceData
from orchestra.utils.tests import random_ascii from orchestra.utils.tests import random_ascii

View File

@ -35,6 +35,9 @@ class SystemUserBackend(ServiceController):
self.append("killall -u %(username)s || true" % context) self.append("killall -u %(username)s || true" % context)
self.append("userdel %(username)s || true" % context) self.append("userdel %(username)s || true" % context)
self.append("groupdel %(username)s || true" % context) self.append("groupdel %(username)s || true" % context)
self.delete_home(context, user)
def delete_home(self, context, user):
if user.is_main: if user.is_main:
# TODO delete instead of this shit # TODO delete instead of this shit
context['deleted'] = context['home'].rstrip('/') + '.deleted' context['deleted'] = context['home'].rstrip('/') + '.deleted'

View File

@ -130,7 +130,8 @@ function install_requirements () {
libxml2-dev \ libxml2-dev \
libxslt1-dev \ libxslt1-dev \
wkhtmltopdf \ wkhtmltopdf \
xvfb" xvfb \
ca-certificates"
PIP="django==1.7 \ PIP="django==1.7 \
django-celery-email==1.0.4 \ django-celery-email==1.0.4 \
@ -139,8 +140,8 @@ function install_requirements () {
IPy==0.81 \ IPy==0.81 \
django-extensions==1.1.1 \ django-extensions==1.1.1 \
django-transaction-signals==1.0.0 \ django-transaction-signals==1.0.0 \
django-celery==3.1.10 \ django-celery==3.1.16 \
celery==3.1.13 \ celery==3.1.16 \
kombu==3.0.23 \ kombu==3.0.23 \
billiard==3.3.0.18 \ billiard==3.3.0.18 \
Markdown==2.4 \ Markdown==2.4 \
@ -153,7 +154,8 @@ function install_requirements () {
jsonfield==0.9.22 \ jsonfield==0.9.22 \
lxml==3.3.5 \ lxml==3.3.5 \
python-dateutil==2.2 \ python-dateutil==2.2 \
django-iban==0.3.0" django-iban==0.3.0 \
requests"
if $testing; then if $testing; then
APT="${APT} \ APT="${APT} \
@ -169,7 +171,6 @@ function install_requirements () {
django-debug-toolbar==1.2.1 \ django-debug-toolbar==1.2.1 \
django-nose==1.2 \ django-nose==1.2 \
sqlparse \ sqlparse \
requests \
--allow-external orchestra-orm --allow-unverified orchestra-orm" --allow-external orchestra-orm --allow-unverified orchestra-orm"
fi fi
@ -180,7 +181,10 @@ function install_requirements () {
update-locale LANG=en_US.UTF-8 update-locale LANG=en_US.UTF-8
fi fi
# Install ca certificates run apt-get update
run apt-get install -y $APT
# Install ca certificates before executing pip install
if [[ ! -e /usr/local/share/ca-certificates/cacert.org ]]; then if [[ ! -e /usr/local/share/ca-certificates/cacert.org ]]; then
mkdir -p /usr/local/share/ca-certificates/cacert.org mkdir -p /usr/local/share/ca-certificates/cacert.org
wget -P /usr/local/share/ca-certificates/cacert.org \ wget -P /usr/local/share/ca-certificates/cacert.org \
@ -189,8 +193,6 @@ function install_requirements () {
update-ca-certificates update-ca-certificates
fi fi
run apt-get update
run apt-get install -y $APT
run pip install $PIP run pip install $PIP
# Some versions of rabbitmq-server will not start automatically by default unless ... # Some versions of rabbitmq-server will not start automatically by default unless ...

View File

@ -66,6 +66,8 @@ TEMPLATE_CONTEXT_PROCESSORS =(
INSTALLED_APPS = ( INSTALLED_APPS = (
# django-orchestra apps # django-orchestra apps
'orchestra', 'orchestra',
'orchestra.apps.accounts',
'orchestra.apps.contacts',
'orchestra.apps.orchestration', 'orchestra.apps.orchestration',
'orchestra.apps.domains', 'orchestra.apps.domains',
'orchestra.apps.systemusers', 'orchestra.apps.systemusers',
@ -73,7 +75,7 @@ INSTALLED_APPS = (
# 'orchestra.apps.users.roles.mail', # 'orchestra.apps.users.roles.mail',
# 'orchestra.apps.users.roles.jabber', # 'orchestra.apps.users.roles.jabber',
# 'orchestra.apps.users.roles.posix', # 'orchestra.apps.users.roles.posix',
'orchestra.apps.mails', 'orchestra.apps.mailboxes',
'orchestra.apps.lists', 'orchestra.apps.lists',
'orchestra.apps.webapps', 'orchestra.apps.webapps',
'orchestra.apps.websites', 'orchestra.apps.websites',
@ -99,7 +101,6 @@ INSTALLED_APPS = (
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',
'passlib.ext.django', 'passlib.ext.django',
'django_nose',
# Django.contrib # Django.contrib
'django.contrib.auth', 'django.contrib.auth',
@ -109,8 +110,7 @@ INSTALLED_APPS = (
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.admin.apps.SimpleAdminConfig', 'django.contrib.admin.apps.SimpleAdminConfig',
'orchestra.apps.accounts', # Last to load
'orchestra.apps.contacts',
'orchestra.apps.resources', 'orchestra.apps.resources',
) )
@ -175,8 +175,8 @@ FLUENT_DASHBOARD_APP_ICONS = {
# Services # Services
'webs/web': 'web.png', 'webs/web': 'web.png',
'mail/address': 'X-office-address-book.png', 'mail/address': 'X-office-address-book.png',
'mails/mailbox': 'email.png', 'mailboxes/mailbox': 'email.png',
'mails/address': 'X-office-address-book.png', 'mailboxes/address': 'X-office-address-book.png',
'lists/list': 'email-alter.png', 'lists/list': 'email-alter.png',
'domains/domain': 'domain.png', 'domains/domain': 'domain.png',
'multitenance/tenant': 'apps.png', 'multitenance/tenant': 'apps.png',

View File

@ -15,9 +15,14 @@ if "celeryd" in sys.argv or 'celeryev' in sys.argv or 'celerybeat' in sys.argv:
DEBUG = False DEBUG = False
# Django debug toolbar # Django debug toolbar
INSTALLED_APPS += ('debug_toolbar', ) INSTALLED_APPS += (
'debug_toolbar',
'django_nose',
)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1', '10.0.3.1',) #10.0.3.1 is the lxcbr0 ip INTERNAL_IPS = (
'127.0.0.1',
'10.0.3.1',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

View File

@ -4,11 +4,17 @@ from django.forms import CheckboxInput
from orchestra import get_version from orchestra import get_version
from orchestra.admin.utils import change_url from orchestra.admin.utils import change_url
from orchestra.utils.apps import isinstalled
register = template.Library() register = template.Library()
@register.filter(name='isinstalled')
def app_is_installed(app_name):
return isinstalled(app_name)
@register.simple_tag(name="version") @register.simple_tag(name="version")
def orchestra_version(): def orchestra_version():
return get_version() return get_version()