Renamed mails apps to mailboxes
This commit is contained in:
parent
0dcdb4ba79
commit
1e8e57c979
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
9
TODO.md
9
TODO.md
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -22,13 +22,12 @@ class BillTypeListFilter(SimpleListFilter):
|
||||||
('proforma', _("Pro-forma")),
|
('proforma', _("Pro-forma")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def value(self):
|
def value(self):
|
||||||
return self.request.path.split('/')[-2]
|
return self.request.path.split('/')[-2]
|
||||||
|
|
||||||
def choices(self, cl):
|
def choices(self, cl):
|
||||||
for lookup, title in self.lookup_choices:
|
for lookup, title in self.lookup_choices:
|
||||||
yield {
|
yield {
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -3,39 +3,58 @@ 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',
|
||||||
'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_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY', 'Spain')
|
||||||
|
|
|
@ -141,6 +141,3 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -93,18 +80,17 @@ 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'
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
|
||||||
|
|
|
@ -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 '"
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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
|
|
@ -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")
|
||||||
|
|
|
@ -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,
|
|
@ -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)
|
|
@ -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')
|
|
@ -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')
|
|
@ -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:
|
|
@ -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()
|
||||||
|
|
|
@ -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 """
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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 ...
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue