Refactores plugins, fixes on mailing contacts and improved resource validation
This commit is contained in:
parent
e962e5d7b2
commit
4732925f63
10
TODO.md
10
TODO.md
|
@ -149,20 +149,16 @@
|
||||||
|
|
||||||
* Resource used_list_display=True, allocated_list_displat=True, allow resources to show up on list_display
|
* Resource used_list_display=True, allocated_list_displat=True, allow resources to show up on list_display
|
||||||
|
|
||||||
* Move plugins back from apps to orchestra main app
|
|
||||||
|
|
||||||
* BackendLog.updated_at (tasks that run over several minutes when finished they do not appear first on the changelist) (like celery tasks.when)
|
* BackendLog.updated_at (tasks that run over several minutes when finished they do not appear first on the changelist) (like celery tasks.when)
|
||||||
|
|
||||||
* Validate a model path exists between resource.content_type and backend.model
|
|
||||||
|
|
||||||
* Periodic task for cleaning old monitoring data
|
* Periodic task for cleaning old monitoring data
|
||||||
|
|
||||||
* Generate reports of Account contracted services
|
|
||||||
|
|
||||||
* Create an admin service_view with icons (like SaaS app)
|
* Create an admin service_view with icons (like SaaS app)
|
||||||
|
|
||||||
* Fix ftp traffic
|
* Fix ftp traffic
|
||||||
|
|
||||||
* Resource graph for each related object
|
* Resource graph for each related object
|
||||||
|
|
||||||
* contacts filter by email_usage fix exact for contains
|
* Rename apache logs ending on .log in order to logrotate easily
|
||||||
|
|
||||||
|
* SaaS wordpress multiple blogs per user? separate users from sites?
|
||||||
|
|
|
@ -3,8 +3,9 @@ from django.core.mail import send_mass_mail
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.utils import change_url
|
from .. import settings
|
||||||
|
|
||||||
|
from .utils import change_url
|
||||||
from .forms import SendEmailForm
|
from .forms import SendEmailForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,18 +14,19 @@ class SendEmail(object):
|
||||||
short_description = _("Send email")
|
short_description = _("Send email")
|
||||||
form = SendEmailForm
|
form = SendEmailForm
|
||||||
template = 'admin/orchestra/generic_confirmation.html'
|
template = 'admin/orchestra/generic_confirmation.html'
|
||||||
|
default_from = settings.ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL
|
||||||
__name__ = 'semd_email'
|
__name__ = 'semd_email'
|
||||||
|
|
||||||
def __call__(self, modeladmin, request, queryset):
|
def __call__(self, modeladmin, request, queryset):
|
||||||
""" make this monster behave like a function """
|
""" make this monster behave like a function """
|
||||||
self.modeladmin = modeladmin
|
self.modeladmin = modeladmin
|
||||||
self.queryset = queryset
|
self.queryset = queryset
|
||||||
opts = modeladmin.model._meta
|
self.opts = modeladmin.model._meta
|
||||||
app_label = opts.app_label
|
app_label = self.opts.app_label
|
||||||
self.context = {
|
self.context = {
|
||||||
'action_name': _("Send email"),
|
'action_name': _("Send email"),
|
||||||
'action_value': self.__name__,
|
'action_value': self.__name__,
|
||||||
'opts': opts,
|
'opts': self.opts,
|
||||||
'app_label': app_label,
|
'app_label': app_label,
|
||||||
'queryset': queryset,
|
'queryset': queryset,
|
||||||
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
|
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
|
||||||
|
@ -34,14 +36,17 @@ class SendEmail(object):
|
||||||
def write_email(self, request):
|
def write_email(self, request):
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
form = self.form()
|
initial={
|
||||||
|
'email_from': self.default_from,
|
||||||
|
'to': ' '.join(self.queryset.values_list('email', flat=True))
|
||||||
|
}
|
||||||
|
form = self.form(initial=initial)
|
||||||
if request.POST.get('post'):
|
if request.POST.get('post'):
|
||||||
form = self.form(request.POST)
|
form = self.form(request.POST, initial=initial)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
options = {
|
options = {
|
||||||
'email_from': form.cleaned_data['email_from'],
|
'email_from': form.cleaned_data['email_from'],
|
||||||
'cc': form.cleaned_data['cc'],
|
'extra_to': form.cleaned_data['extra_to'],
|
||||||
'bcc': form.cleaned_data['bcc'],
|
|
||||||
'subject': form.cleaned_data['subject'],
|
'subject': form.cleaned_data['subject'],
|
||||||
'message': form.cleaned_data['message'],
|
'message': form.cleaned_data['message'],
|
||||||
|
|
||||||
|
@ -50,7 +55,7 @@ class SendEmail(object):
|
||||||
opts = self.modeladmin.model._meta
|
opts = self.modeladmin.model._meta
|
||||||
app_label = opts.app_label
|
app_label = opts.app_label
|
||||||
self.context.update({
|
self.context.update({
|
||||||
'title': _("Send e-mail to contacts"),
|
'title': _("Send e-mail to %s") % self.opts.verbose_name_plural,
|
||||||
'content_title': "",
|
'content_title': "",
|
||||||
'form': form,
|
'form': form,
|
||||||
'submit_value': _("Continue"),
|
'submit_value': _("Continue"),
|
||||||
|
@ -61,31 +66,36 @@ class SendEmail(object):
|
||||||
def confirm_email(self, request, **options):
|
def confirm_email(self, request, **options):
|
||||||
num = len(self.queryset)
|
num = len(self.queryset)
|
||||||
email_from = options['email_from']
|
email_from = options['email_from']
|
||||||
bcc = options['bcc']
|
extra_to = options['extra_to']
|
||||||
to = options['cc']
|
|
||||||
subject = options['subject']
|
subject = options['subject']
|
||||||
message = options['message']
|
message = options['message']
|
||||||
# The user has already confirmed
|
# The user has already confirmed
|
||||||
if request.POST.get('post') == 'email_confirmation':
|
if request.POST.get('post') == 'email_confirmation':
|
||||||
|
emails = []
|
||||||
for contact in self.queryset.all():
|
for contact in self.queryset.all():
|
||||||
to.append(contact.email)
|
emails.append((subject, message, email_from, [contact.email]))
|
||||||
send_mass_mail(subject, message, email_from, to, bcc)
|
if extra_to:
|
||||||
|
emails.append((subject, message, email_from, extra_to))
|
||||||
|
send_mass_mail(emails)
|
||||||
msg = ungettext(
|
msg = ungettext(
|
||||||
_("Message has been sent to %s.") % str(contact),
|
_("Message has been sent to %s.") % str(contact),
|
||||||
_("Message has been sent to %i contacts.") % num,
|
_("Message has been sent to %i %s.") % (num, self.opts.verbose_name_plural),
|
||||||
num
|
num
|
||||||
)
|
)
|
||||||
self.modeladmin.message_user(request, msg)
|
self.modeladmin.message_user(request, msg)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
form = self.form(initial={
|
form = self.form(initial={
|
||||||
|
'email_from': email_from,
|
||||||
|
'extra_to': ', '.join(extra_to),
|
||||||
'subject': subject,
|
'subject': subject,
|
||||||
'message': message
|
'message': message
|
||||||
})
|
})
|
||||||
self.context.update({
|
self.context.update({
|
||||||
'title': _("Are you sure?"),
|
'title': _("Are you sure?"),
|
||||||
'content_message': _(
|
'content_message': _(
|
||||||
"Are you sure you want to send the following message to the following contacts?"),
|
"Are you sure you want to send the following message to the following %s?"
|
||||||
|
) % self.opts.verbose_name_plural,
|
||||||
'display_objects': ["%s (%s)" % (contact, contact.email) for contact in self.queryset],
|
'display_objects': ["%s (%s)" % (contact, contact.email) for contact in self.queryset],
|
||||||
'form': form,
|
'form': form,
|
||||||
'subject': subject,
|
'subject': subject,
|
|
@ -2,10 +2,13 @@ from functools import partial
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.admin import helpers
|
from django.contrib.admin import helpers
|
||||||
|
from django.core import validators
|
||||||
from django.forms.models import modelformset_factory, BaseModelFormSet
|
from django.forms.models import modelformset_factory, BaseModelFormSet
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget
|
||||||
|
|
||||||
from ..core.validators import validate_password
|
from ..core.validators import validate_password
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,3 +132,39 @@ class AdminPasswordChangeForm(forms.Form):
|
||||||
return ['password']
|
return ['password']
|
||||||
changed_data = property(_get_changed_data)
|
changed_data = property(_get_changed_data)
|
||||||
|
|
||||||
|
|
||||||
|
class SendEmailForm(forms.Form):
|
||||||
|
email_from = forms.EmailField(label=_("From"),
|
||||||
|
widget=forms.TextInput(attrs={'size':'118'}))
|
||||||
|
to = forms.CharField(label="To", required=False,
|
||||||
|
widget=ShowTextWidget())
|
||||||
|
extra_to = forms.CharField(label="To (extra)", required=False,
|
||||||
|
widget=forms.TextInput(attrs={'size':'118'}))
|
||||||
|
subject = forms.CharField(label=_("Subject"),
|
||||||
|
widget=forms.TextInput(attrs={'size':'118'}))
|
||||||
|
message = forms.CharField(label=_("Message"),
|
||||||
|
widget=forms.Textarea(attrs={'cols': 118, 'rows': 15}))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(SendEmailForm, self).__init__(*args, **kwargs)
|
||||||
|
initial = kwargs.get('initial')
|
||||||
|
if 'to' in initial:
|
||||||
|
self.fields['to'].widget = ReadOnlyWidget(initial['to'])
|
||||||
|
else:
|
||||||
|
self.fields.pop('to')
|
||||||
|
|
||||||
|
def clean_comma_separated_emails(self, value):
|
||||||
|
clean_value = []
|
||||||
|
for email in value.split(','):
|
||||||
|
email = email.strip()
|
||||||
|
if email:
|
||||||
|
try:
|
||||||
|
validators.validate_email(email)
|
||||||
|
except validators.ValidationError:
|
||||||
|
raise validators.ValidationError("Comma separated email addresses.")
|
||||||
|
clean_value.append(email)
|
||||||
|
return clean_value
|
||||||
|
|
||||||
|
def clean_extra_to(self):
|
||||||
|
extra_to = self.cleaned_data['extra_to']
|
||||||
|
return self.clean_comma_separated_emails(extra_to)
|
||||||
|
|
|
@ -39,13 +39,22 @@ list_contacts.verbose_name = _("List contacts")
|
||||||
|
|
||||||
def service_report(modeladmin, request, queryset):
|
def service_report(modeladmin, request, queryset):
|
||||||
accounts = []
|
accounts = []
|
||||||
for account in queryset:
|
fields = []
|
||||||
|
# First we get related manager names to fire a prefetch related
|
||||||
|
for name, field in queryset.model._meta._name_map.iteritems():
|
||||||
|
model = field[0].model
|
||||||
|
if model in services.get() and model != queryset.model:
|
||||||
|
fields.append((model, name))
|
||||||
|
sorted(fields, key=lambda i: i[0]._meta.verbose_name_plural.lower())
|
||||||
|
fields = [field for model, field in fields]
|
||||||
|
|
||||||
|
for account in queryset.prefetch_related(*fields):
|
||||||
items = []
|
items = []
|
||||||
for service in services.get():
|
for field in fields:
|
||||||
if service != type(account):
|
related_manager = getattr(account, field)
|
||||||
items.append((service._meta, service.objects.filter(account=account)))
|
items.append((related_manager.model._meta, related_manager.all()))
|
||||||
sorted(items, key=lambda i: i[0].verbose_name_plural.lower())
|
|
||||||
accounts.append((account, items))
|
accounts.append((account, items))
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'accounts': accounts,
|
'accounts': accounts,
|
||||||
'date': timezone.now().today()
|
'date': timezone.now().today()
|
||||||
|
|
|
@ -13,6 +13,7 @@ from django.utils.six.moves.urllib.parse import parse_qsl
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||||
|
from orchestra.admin.actions import SendEmail
|
||||||
from orchestra.admin.utils import wrap_admin_view, admin_link, set_url_query, change_url
|
from orchestra.admin.utils import wrap_admin_view, admin_link, set_url_query, change_url
|
||||||
from orchestra.core import services, accounts
|
from orchestra.core import services, accounts
|
||||||
from orchestra.forms import UserChangeForm
|
from orchestra.forms import UserChangeForm
|
||||||
|
@ -61,7 +62,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
|
||||||
filter_horizontal = ()
|
filter_horizontal = ()
|
||||||
change_readonly_fields = ('username', 'main_systemuser_link')
|
change_readonly_fields = ('username', 'main_systemuser_link')
|
||||||
change_form_template = 'admin/accounts/account/change_form.html'
|
change_form_template = 'admin/accounts/account/change_form.html'
|
||||||
actions = [disable, list_contacts, service_report]
|
actions = [disable, list_contacts, service_report, SendEmail()]
|
||||||
change_view_actions = [disable, service_report]
|
change_view_actions = [disable, service_report]
|
||||||
list_select_related = ('billcontact',)
|
list_select_related = ('billcontact',)
|
||||||
ordering = ()
|
ordering = ()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load utils %}
|
{% load utils i18n %}
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{% block title %}Service Report{% endblock %}</title>
|
<title>{% block title %}Account service report{% endblock %}</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
@ -11,11 +11,11 @@
|
||||||
float: none !important;
|
float: none !important;
|
||||||
font-family: Arial, 'Liberation Sans', 'DejaVu Sans', sans-serif;
|
font-family: Arial, 'Liberation Sans', 'DejaVu Sans', sans-serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #2E2E2E;
|
color: #444;
|
||||||
}
|
}
|
||||||
#date {
|
#date {
|
||||||
float: right;
|
float: right;
|
||||||
color: #666;
|
color: rgb(102, 102, 102);
|
||||||
}
|
}
|
||||||
.account-content {
|
.account-content {
|
||||||
margin: 0px 0px 40px 20px;
|
margin: 0px 0px 40px 20px;
|
||||||
|
@ -23,27 +23,38 @@
|
||||||
.item-title {
|
.item-title {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
}
|
}
|
||||||
.items-ul {
|
.items-ul {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 5px 0px 10px 20px;
|
margin: 5px 0px 10px 20px;
|
||||||
}
|
}
|
||||||
|
.related {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
margin-top: -9px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgb(91, 128, 178);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="date">Service report generated on {{ date | date }}</div>
|
<div id="date">{% trans "Service report generated on" %} {{ date | date }}</div>
|
||||||
{% for account, items in accounts %}
|
{% for account, items in accounts %}
|
||||||
<h3>{{ account.get_full_name }} ({{ account.username }})</h3>
|
<h3>{{ account.get_full_name }} - <a href="{{ account|admin_url }}">{{ account.username }}</a></h3>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="account-content">
|
<div class="account-content">
|
||||||
{{ account.get_type_display }} account registered on {{ account.date_joined | date }}<br>
|
{{ account.get_type_display }} {% trans "account registered on" %} {{ account.date_joined | date }}<br>
|
||||||
<ul class="items-ul">
|
<ul class="items-ul">
|
||||||
{% for opts, related in items %}
|
{% for opts, related in items %}
|
||||||
<li class="item-title">{{ opts.verbose_name_plural|capfirst }}</li>
|
<li class="item-title">{{ opts.verbose_name_plural|capfirst }}</li>
|
||||||
<ul>
|
<ul>
|
||||||
{% for obj in related %}
|
{% for obj in related %}
|
||||||
<li>{{ obj }}{% if not obj|isactive %} (disabled){% endif %}</li>
|
<li class="related"><a href="{{ obj|admin_url }}">{{ obj }}</a>{% if not obj|isactive %} ({% trans "disabled" %}){% endif %}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -3,11 +3,12 @@ from django.contrib import admin
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import AtLeastOneRequiredInlineFormSet, ExtendedModelAdmin
|
from orchestra.admin import AtLeastOneRequiredInlineFormSet, ExtendedModelAdmin
|
||||||
|
from orchestra.admin.actions import SendEmail
|
||||||
from orchestra.admin.utils import insertattr, admin_link, change_url
|
from orchestra.admin.utils import insertattr, admin_link, change_url
|
||||||
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 .actions import SendEmail
|
from .filters import EmailUsageListFilter
|
||||||
from .models import Contact
|
from .models import Contact
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
'dispaly_name', 'email', 'phone', 'phone2', 'country', 'account_link'
|
'dispaly_name', 'email', 'phone', 'phone2', 'country', 'account_link'
|
||||||
)
|
)
|
||||||
# TODO email usage custom filter contains
|
# TODO email usage custom filter contains
|
||||||
list_filter = ('email_usage',)
|
list_filter = (EmailUsageListFilter,)
|
||||||
search_fields = (
|
search_fields = (
|
||||||
'account__username', 'account__full_name', 'short_name', 'full_name', 'phone', 'phone2',
|
'account__username', 'account__full_name', 'short_name', 'full_name', 'phone', 'phone2',
|
||||||
'email'
|
'email'
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from django.contrib.admin import SimpleListFilter
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .models import Contact
|
||||||
|
|
||||||
|
|
||||||
|
class EmailUsageListFilter(SimpleListFilter):
|
||||||
|
title = _("email usages")
|
||||||
|
parameter_name = 'email_usages'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return Contact.email_usage.field.choices
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
value = self.value().split(',')
|
||||||
|
return queryset.filter(email_usages=value)
|
|
@ -1,36 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.core import validators
|
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.forms.widgets import ShowTextWidget
|
|
||||||
|
|
||||||
from . import settings
|
|
||||||
|
|
||||||
|
|
||||||
class SendEmailForm(forms.Form):
|
|
||||||
email_from = forms.EmailField(label=_("From"),
|
|
||||||
initial=settings.CONTACTS_DEFAULT_FROM_EMAIL,
|
|
||||||
widget=forms.TextInput(attrs={'size':'118'}))
|
|
||||||
cc = forms.CharField(label="CC", required=False,
|
|
||||||
widget=forms.TextInput(attrs={'size':'118'}))
|
|
||||||
bcc = forms.CharField(label="BCC", required=False,
|
|
||||||
widget=forms.TextInput(attrs={'size':'118'}))
|
|
||||||
subject = forms.CharField(label=_("Subject"),
|
|
||||||
widget=forms.TextInput(attrs={'size':'118'}))
|
|
||||||
message = forms.CharField(label=_("Message"),
|
|
||||||
widget=forms.Textarea(attrs={'cols': 118, 'rows': 15}))
|
|
||||||
|
|
||||||
def clean_space_separated_emails(self, value):
|
|
||||||
value = value.split()
|
|
||||||
for email in value:
|
|
||||||
try:
|
|
||||||
validators.validate_email(email)
|
|
||||||
except validators.ValidationError:
|
|
||||||
raise validators.ValidationError("Space separated emails.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def clean_cc(self):
|
|
||||||
return self.clean_space_separated_emails(self.cleaned_data['cc'])
|
|
||||||
|
|
||||||
def clean_bcc(self):
|
|
||||||
return self.clean_space_separated_emails(self.cleaned_data['bcc'])
|
|
|
@ -2,18 +2,22 @@ from django.conf import settings
|
||||||
from django_countries import data
|
from django_countries import data
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES',
|
CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES', (
|
||||||
('SUPPORT', 'ADMIN', 'BILLING', 'TECH', 'ADDS', 'EMERGENCY')
|
'SUPPORT',
|
||||||
)
|
'ADMIN',
|
||||||
|
'BILLING',
|
||||||
|
'TECH',
|
||||||
|
'ADDS',
|
||||||
|
'EMERGENCY'
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY', 'Barcelona')
|
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY', 'Barcelona')
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', ((k,v) for k,v in data.COUNTRIES.iteritems()))
|
CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', (
|
||||||
|
(k,v) for k,v in data.COUNTRIES.iteritems()
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY', 'ES')
|
CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY', 'ES')
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_FROM_EMAIL = getattr(settings, 'CONTACTS_DEFAULT_FROM_EMAIL', 'support@orchestra.lan')
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class TopDomainListFilter(SimpleListFilter):
|
class TopDomainListFilter(SimpleListFilter):
|
||||||
""" Filter Nodes by group according to request.user """
|
""" Filter Nodes by group according to request.user """
|
||||||
title = _("Top domains")
|
title = _("top domains")
|
||||||
parameter_name = 'top_domain'
|
parameter_name = 'top_domain'
|
||||||
|
|
||||||
def lookups(self, request, model_admin):
|
def lookups(self, request, model_admin):
|
||||||
|
|
|
@ -8,8 +8,8 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
from orchestra.admin.utils import admin_link
|
from orchestra.admin.utils import admin_link
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
from orchestra.apps.plugins import PluginModelAdapter
|
from orchestra.plugins import PluginModelAdapter
|
||||||
from orchestra.apps.plugins.admin import SelectPluginAdminMixin
|
from orchestra.plugins.admin import SelectPluginAdminMixin
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .models import MiscService, Miscellaneous
|
from .models import MiscService, Miscellaneous
|
||||||
|
@ -26,7 +26,9 @@ class MiscServiceAdmin(ExtendedModelAdmin):
|
||||||
)
|
)
|
||||||
list_editable = ('is_active',)
|
list_editable = ('is_active',)
|
||||||
list_filter = ('has_identifier', 'has_amount', 'is_active')
|
list_filter = ('has_identifier', 'has_amount', 'is_active')
|
||||||
fields = ('verbose_name', 'name', 'description', 'has_identifier', 'has_amount', 'is_active')
|
fields = (
|
||||||
|
'verbose_name', 'name', 'description', 'has_identifier', 'has_amount', 'is_active'
|
||||||
|
)
|
||||||
prepopulated_fields = {'name': ('verbose_name',)}
|
prepopulated_fields = {'name': ('verbose_name',)}
|
||||||
change_readonly_fields = ('name',)
|
change_readonly_fields = ('name',)
|
||||||
|
|
||||||
|
@ -51,9 +53,12 @@ class MiscServiceAdmin(ExtendedModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelAdmin):
|
class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelAdmin):
|
||||||
list_display = ('__unicode__', 'service_link', 'amount', 'dispaly_active', 'account_link')
|
list_display = (
|
||||||
|
'__unicode__', 'service_link', 'amount', 'dispaly_active', 'account_link'
|
||||||
|
)
|
||||||
list_filter = ('service__name', 'is_active')
|
list_filter = ('service__name', 'is_active')
|
||||||
list_select_related = ('service', 'account')
|
list_select_related = ('service', 'account')
|
||||||
|
search_fields = ('identifier', 'description')
|
||||||
plugin_field = 'service'
|
plugin_field = 'service'
|
||||||
plugin = MiscServicePlugin
|
plugin = MiscServicePlugin
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.apps import plugins
|
from orchestra import plugins
|
||||||
|
|
||||||
from . import methods
|
from . import methods
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra.admin import ChangeViewActionsMixin, ExtendedModelAdmin
|
from orchestra.admin import ChangeViewActionsMixin, ExtendedModelAdmin
|
||||||
from orchestra.admin.utils import admin_colored, admin_link
|
from orchestra.admin.utils import admin_colored, admin_link
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
|
||||||
from orchestra.apps.plugins.admin import SelectPluginAdminMixin
|
from orchestra.plugins.admin import SelectPluginAdminMixin
|
||||||
|
|
||||||
from . import actions
|
from . import actions
|
||||||
from .methods import PaymentMethod
|
from .methods import PaymentMethod
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.apps.plugins.forms import PluginDataForm
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
|
||||||
from .options import PaymentMethod
|
from .options import PaymentMethod
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from dateutil import relativedelta
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from orchestra.apps import plugins
|
from orchestra import plugins
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from django_iban.forms import IBANFormField
|
||||||
from django_iban.validators import IBANValidator, IBAN_COUNTRY_CODE_LENGTH
|
from django_iban.validators import IBANValidator, IBAN_COUNTRY_CODE_LENGTH
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.apps.plugins.forms import PluginDataForm
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
|
||||||
from .. import settings
|
from .. import settings
|
||||||
from .options import PaymentMethod
|
from .options import PaymentMethod
|
||||||
|
|
|
@ -86,6 +86,21 @@ class Resource(models.Model):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.verbose_name = self.verbose_name.strip()
|
self.verbose_name = self.verbose_name.strip()
|
||||||
|
# Validate that model path exists between ct and each monitor.model
|
||||||
|
monitor_errors = []
|
||||||
|
for monitor in self.monitors:
|
||||||
|
try:
|
||||||
|
self.get_model_path(monitor)
|
||||||
|
except (RuntimeError, LookupError):
|
||||||
|
monitor_errors.append(monitor)
|
||||||
|
if monitor_errors:
|
||||||
|
raise validators.ValidationError({
|
||||||
|
'monitors': [
|
||||||
|
_("Path does not exists between '%s' and '%s'") % (
|
||||||
|
get_model(ServiceMonitor.get_backend(monitor).model)._meta.model_name,
|
||||||
|
self.content_type.model_class()._meta.model_name,
|
||||||
|
) for error in monitor_errors
|
||||||
|
]})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
created = not self.pk
|
created = not self.pk
|
||||||
|
@ -99,6 +114,13 @@ class Resource(models.Model):
|
||||||
super(Resource, self).delete(*args, **kwargs)
|
super(Resource, self).delete(*args, **kwargs)
|
||||||
name = 'monitor.%s' % str(self)
|
name = 'monitor.%s' % str(self)
|
||||||
|
|
||||||
|
def get_model_path(self, monitor):
|
||||||
|
""" returns a model path between self.content_type and monitor.model """
|
||||||
|
resource_model = self.content_type.model_class()
|
||||||
|
model_path = ServiceMonitor.get_backend(monitor).model
|
||||||
|
monitor_model = get_model(model_path)
|
||||||
|
return get_model_field_path(monitor_model, resource_model)
|
||||||
|
|
||||||
def sync_periodic_task(self):
|
def sync_periodic_task(self):
|
||||||
name = 'monitor.%s' % str(self)
|
name = 'monitor.%s' % str(self)
|
||||||
if self.pk and self.crontab:
|
if self.pk and self.crontab:
|
||||||
|
@ -190,17 +212,14 @@ class ResourceData(models.Model):
|
||||||
today = timezone.now()
|
today = timezone.now()
|
||||||
datasets = []
|
datasets = []
|
||||||
for monitor in resource.monitors:
|
for monitor in resource.monitors:
|
||||||
resource_model = self.content_type.model_class()
|
path = self.resource.get_model_path(monitor)
|
||||||
model_path = ServiceMonitor.get_backend(monitor).model
|
if path == []:
|
||||||
monitor_model = get_model(model_path)
|
|
||||||
if resource_model == monitor_model:
|
|
||||||
dataset = MonitorData.objects.filter(
|
dataset = MonitorData.objects.filter(
|
||||||
monitor=monitor,
|
monitor=monitor,
|
||||||
content_type=self.content_type_id,
|
content_type=self.content_type_id,
|
||||||
object_id=self.object_id
|
object_id=self.object_id
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
path = get_model_field_path(monitor_model, resource_model)
|
|
||||||
fields = '__'.join(path)
|
fields = '__'.join(path)
|
||||||
objects = monitor_model.objects.filter(**{fields: self.object_id})
|
objects = monitor_model.objects.filter(**{fields: self.object_id})
|
||||||
pks = objects.values_list('id', flat=True)
|
pks = objects.values_list('id', flat=True)
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
def validate_scale(value):
|
def validate_scale(value):
|
||||||
try:
|
try:
|
||||||
int(eval(value))
|
int(eval(value))
|
||||||
except ValueError:
|
except Exception, e:
|
||||||
raise ValidationError(_("%s value is not a valid scale expression"))
|
raise ValidationError(
|
||||||
|
_("'%s' is not a valid scale expression. (%s)") % (value, str(e))
|
||||||
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.contrib import admin
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
from orchestra.apps.plugins.admin import SelectPluginAdminMixin
|
from orchestra.plugins.admin import SelectPluginAdminMixin
|
||||||
|
|
||||||
from .models import SaaS
|
from .models import SaaS
|
||||||
from .services import SoftwareService
|
from .services import SoftwareService
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.apps.plugins.forms import PluginDataForm
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
|
||||||
from .options import SoftwareService
|
from .options import SoftwareService
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ from django.core.exceptions import ValidationError
|
||||||
from django.utils.safestring import mark_safe
|
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.apps import plugins
|
from orchestra import plugins
|
||||||
from orchestra.apps.plugins.forms import PluginDataForm
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
from orchestra.forms import widgets
|
from orchestra.forms import widgets
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
|
@ -72,6 +72,7 @@ class SoftwareServiceForm(PluginDataForm):
|
||||||
obj.set_password(self.cleaned_data["password1"])
|
obj.set_password(self.cleaned_data["password1"])
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class SoftwareService(plugins.Plugin):
|
class SoftwareService(plugins.Plugin):
|
||||||
form = SoftwareServiceForm
|
form = SoftwareServiceForm
|
||||||
serializer = None
|
serializer = None
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.apps import plugins
|
from orchestra import plugins
|
||||||
from orchestra.utils.humanize import text2int
|
from orchestra.utils.humanize import text2int
|
||||||
from orchestra.utils.python import AttrDict
|
from orchestra.utils.python import AttrDict
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,6 @@ INSTALLED_APPS = (
|
||||||
'orchestra.apps.miscellaneous',
|
'orchestra.apps.miscellaneous',
|
||||||
'orchestra.apps.bills',
|
'orchestra.apps.bills',
|
||||||
'orchestra.apps.payments',
|
'orchestra.apps.payments',
|
||||||
'orchestra.apps.plugins',
|
|
||||||
|
|
||||||
# Third-party apps
|
# Third-party apps
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
|
|
|
@ -47,3 +47,4 @@ def get_model_field_path(origin, target):
|
||||||
new_path = list(path)
|
new_path = list(path)
|
||||||
new_path.append(field.name)
|
new_path.append(field.name)
|
||||||
queue.append((new_model, new_path))
|
queue.append((new_model, new_path))
|
||||||
|
raise LookupError("Path does not exists between '%s' and '%s' models" % (origin, target))
|
||||||
|
|
|
@ -31,3 +31,8 @@ API_ROOT_VIEW = getattr(settings, 'API_ROOT_VIEW', 'orchestra.api.root.APIRoot')
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_MIGRATION_MODE = getattr(settings, 'ORCHESTRA_MIGRATION_MODE', False)
|
ORCHESTRA_MIGRATION_MODE = getattr(settings, 'ORCHESTRA_MIGRATION_MODE', False)
|
||||||
|
|
||||||
|
|
||||||
|
ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL = getattr(settings, 'ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL',
|
||||||
|
'support@orchestra.lan'
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue