Fixes on payments and saas domains
This commit is contained in:
parent
764301555c
commit
835a4ab872
|
@ -101,16 +101,16 @@ class PaymentStateListFilter(SimpleListFilter):
|
|||
elif self.value() == 'PAID':
|
||||
zeros = queryset.filter(approx_total=0, approx_total__isnull=True)
|
||||
zeros = zeros.values_list('id', flat=True)
|
||||
ammounts = Transaction.objects.exclude(bill_id__in=zeros).secured().group_by('bill_id')
|
||||
amounts = Transaction.objects.exclude(bill_id__in=zeros).secured().group_by('bill_id')
|
||||
paid = []
|
||||
relevant = queryset.exclude(approx_total=0, approx_total__isnull=True, is_open=True)
|
||||
for bill_id, total in relevant.values_list('id', 'approx_total'):
|
||||
try:
|
||||
ammount = sum([t.ammount for t in ammounts[bill_id]])
|
||||
amount = sum([t.amount for t in amounts[bill_id]])
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if abs(total) <= abs(ammount):
|
||||
if abs(total) <= abs(amount):
|
||||
paid.append(bill_id)
|
||||
return queryset.filter(
|
||||
Q(approx_total=0) |
|
||||
|
@ -120,8 +120,9 @@ class PaymentStateListFilter(SimpleListFilter):
|
|||
elif self.value() == 'PENDING':
|
||||
has_transaction = queryset.exclude(transactions__isnull=True)
|
||||
non_rejected = has_transaction.exclude(transactions__state=Transaction.REJECTED)
|
||||
non_rejected = non_rejected.values_list('id', flat=True).distinct()
|
||||
return queryset.filter(pk__in=non_rejected)
|
||||
paid = non_rejected.exclude(transactions__state=Transaction.SECURED)
|
||||
paid = paid.values_list('id', flat=True).distinct()
|
||||
return queryset.filter(pk__in=paid)
|
||||
elif self.value() == 'BAD_DEBT':
|
||||
closed = queryset.filter(is_open=False).exclude(approx_total=0)
|
||||
return closed.filter(
|
||||
|
|
|
@ -94,7 +94,13 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
link = '<a href="%s">%s %s</a>' % (admin_url, website.name, site_link)
|
||||
links.append(link)
|
||||
return '<br>'.join(links)
|
||||
return _("No website")
|
||||
context = {
|
||||
'title': _("View on site"),
|
||||
'url': 'http://%s' % domain.name,
|
||||
'image': '<img src="%s"></img>' % static('orchestra/images/view-on-site.png'),
|
||||
}
|
||||
site_link = '<a href="%(url)s" title="%(title)s">%(image)s</a>' % context
|
||||
return _("No website %s") % site_link
|
||||
display_websites.admin_order_field = 'websites__name'
|
||||
display_websites.short_description = _("Websites")
|
||||
display_websites.allow_tags = True
|
||||
|
|
|
@ -105,7 +105,6 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
source_link = admin_link('source')
|
||||
process_link = admin_link('process', short_description=_("proc"))
|
||||
account_link = admin_link('bill__account')
|
||||
display_state = admin_colored('state', colors=STATE_COLORS)
|
||||
|
||||
def get_change_view_actions(self, obj=None):
|
||||
actions = super(TransactionAdmin, self).get_change_view_actions()
|
||||
|
@ -121,6 +120,15 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
return []
|
||||
return [action for action in actions if action.__name__ not in exclude]
|
||||
|
||||
def display_state(self, obj):
|
||||
state = admin_colored('state', colors=STATE_COLORS)(obj)
|
||||
help_text = obj.get_state_help()
|
||||
state = state.replace('<span ', '<span title="%s" ' % help_text)
|
||||
return state
|
||||
display_state.admin_order_field = 'state'
|
||||
display_state.short_description = _("State")
|
||||
display_state.allow_tags = True
|
||||
|
||||
|
||||
class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||
list_display = ('id', 'file_url', 'display_transactions', 'created_at')
|
||||
|
|
|
@ -17,6 +17,7 @@ class PaymentMethod(plugins.Plugin):
|
|||
process_credit = False
|
||||
due_delta = relativedelta.relativedelta(months=1)
|
||||
plugin_field = 'method'
|
||||
state_help = {}
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
|
|
|
@ -43,6 +43,12 @@ class SEPADirectDebit(PaymentMethod):
|
|||
form = SEPADirectDebitForm
|
||||
serializer = SEPADirectDebitSerializer
|
||||
due_delta = datetime.timedelta(days=5)
|
||||
state_help = {
|
||||
'WAITTING_PROCESSING': _("The transaction is created and requires the generation of "
|
||||
"the SEPA direct debit XML file."),
|
||||
'WAITTING_EXECUTION': _("SEPA Direct Debit XML file is generated but needs to be sent "
|
||||
"to the financial institution."),
|
||||
}
|
||||
|
||||
def get_bill_message(self):
|
||||
context = {
|
||||
|
|
|
@ -98,6 +98,16 @@ class Transaction(models.Model):
|
|||
(SECURED, _("Secured")),
|
||||
(REJECTED, _("Rejected")),
|
||||
)
|
||||
STATE_HELP = {
|
||||
WAITTING_PROCESSING: _("The transaction is created and requires processing by the "
|
||||
"specific payment method."),
|
||||
WAITTING_EXECUTION: _("The transaction is processed and its pending execution on "
|
||||
"the related financial institution."),
|
||||
EXECUTED: _("The transaction is executed on the financial institution."),
|
||||
SECURED: _("The transaction ammount is secured."),
|
||||
REJECTED: _("The transaction has failed and the ammount is lost, a new transaction "
|
||||
"should be created for recharging."),
|
||||
}
|
||||
|
||||
bill = models.ForeignKey('bills.bill', verbose_name=_("bill"),
|
||||
related_name='transactions')
|
||||
|
@ -127,22 +137,20 @@ class Transaction(models.Model):
|
|||
if amount >= self.bill.total:
|
||||
raise ValidationError(_("New transactions can not be allocated for this bill."))
|
||||
|
||||
def check_state(self, *args):
|
||||
if self.state not in args:
|
||||
raise TypeError("Transaction not in %s" % ' or '.join(args))
|
||||
def get_state_help(self):
|
||||
if self.source:
|
||||
return self.source.method_instance.state_help.get(self.state) or self.STATE_HELP.get(self.state)
|
||||
return self.STATE_HELP.get(self.state)
|
||||
|
||||
def mark_as_processed(self):
|
||||
self.check_state(self.WAITTING_PROCESSING)
|
||||
self.state = self.WAITTING_EXECUTION
|
||||
self.save(update_fields=('state', 'modified_at'))
|
||||
|
||||
def mark_as_executed(self):
|
||||
self.check_state(self.WAITTING_EXECUTION)
|
||||
self.state = self.EXECUTED
|
||||
self.save(update_fields=('state', 'modified_at'))
|
||||
|
||||
def mark_as_secured(self):
|
||||
self.check_state(self.EXECUTED)
|
||||
self.state = self.SECURED
|
||||
self.save(update_fields=('state', 'modified_at'))
|
||||
|
||||
|
@ -178,26 +186,19 @@ class TransactionProcess(models.Model):
|
|||
def __str__(self):
|
||||
return '#%i' % self.id
|
||||
|
||||
def check_state(self, *args):
|
||||
if self.state not in args:
|
||||
raise TypeError("Transaction process not in %s" % ' or '.join(args))
|
||||
|
||||
def mark_as_executed(self):
|
||||
self.check_state(self.CREATED)
|
||||
self.state = self.EXECUTED
|
||||
for transaction in self.transactions.all():
|
||||
transaction.mark_as_executed()
|
||||
self.save(update_fields=('state',))
|
||||
|
||||
def abort(self):
|
||||
self.check_state(self.CREATED, self.EXCECUTED)
|
||||
self.state = self.ABORTED
|
||||
for transaction in self.transaction.all():
|
||||
transaction.mark_as_aborted()
|
||||
self.save(update_fields=('state',))
|
||||
|
||||
def commit(self):
|
||||
self.check_state(self.CREATED, self.EXECUTED)
|
||||
self.state = self.COMMITED
|
||||
for transaction in self.transactions.processing():
|
||||
transaction.mark_as_secured()
|
||||
|
|
|
@ -17,24 +17,24 @@ class WordpressMuBackend(ServiceController):
|
|||
model = 'saas.SaaS'
|
||||
default_route_match = "saas.service == 'wordpress'"
|
||||
doc_settings = (settings,
|
||||
('SAAS_WORDPRESS_ADMIN_PASSWORD', 'SAAS_WORDPRESS_BASE_URL')
|
||||
('SAAS_WORDPRESS_ADMIN_PASSWORD', 'SAAS_WORDPRESS_MAIN_URL')
|
||||
)
|
||||
|
||||
def login(self, session):
|
||||
base_url = self.get_base_url()
|
||||
login_url = base_url + '/wp-login.php'
|
||||
main_url = self.get_main_url()
|
||||
login_url = main_url + '/wp-login.php'
|
||||
login_data = {
|
||||
'log': 'admin',
|
||||
'pwd': settings.SAAS_WORDPRESS_ADMIN_PASSWORD,
|
||||
'redirect_to': '/wp-admin/'
|
||||
}
|
||||
response = session.post(login_url, data=login_data)
|
||||
if response.url != base_url + '/wp-admin/':
|
||||
if response.url != main_url + '/wp-admin/':
|
||||
raise IOError("Failure login to remote application")
|
||||
|
||||
def get_base_url(self):
|
||||
base_url = settings.SAAS_WORDPRESS_BASE_URL
|
||||
return base_url.rstrip('/')
|
||||
def get_main_url(self):
|
||||
main_url = settings.SAAS_WORDPRESS_MAIN_URL
|
||||
return main_url.rstrip('/')
|
||||
|
||||
def validate_response(self, response):
|
||||
if response.status_code != 200:
|
||||
|
@ -42,7 +42,7 @@ class WordpressMuBackend(ServiceController):
|
|||
raise RuntimeError(errors[0] if errors else 'Unknown %i error' % response.status_code)
|
||||
|
||||
def get_id(self, session, saas):
|
||||
search = self.get_base_url()
|
||||
search = self.get_main_url()
|
||||
search += '/wp-admin/network/sites.php?s=%s&action=blogs' % saas.name
|
||||
regex = re.compile(
|
||||
'<a href="http://[\.\-\w]+/wp-admin/network/site-info\.php\?id=([0-9]+)"\s+'
|
||||
|
@ -69,7 +69,7 @@ class WordpressMuBackend(ServiceController):
|
|||
try:
|
||||
self.get_id(session, saas)
|
||||
except RuntimeError:
|
||||
url = self.get_base_url()
|
||||
url = self.get_main_url()
|
||||
url += '/wp-admin/network/site-new.php'
|
||||
content = session.get(url).content.decode('utf8')
|
||||
|
||||
|
@ -97,7 +97,7 @@ class WordpressMuBackend(ServiceController):
|
|||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
delete = self.get_base_url()
|
||||
delete = self.get_main_url()
|
||||
delete += '/wp-admin/network/sites.php?action=confirm&action2=deleteblog'
|
||||
delete += '&id=%d&_wpnonce=%s' % (id, wpnonce)
|
||||
|
||||
|
@ -110,7 +110,7 @@ class WordpressMuBackend(ServiceController):
|
|||
'_wpnonce': wpnonce,
|
||||
'_wp_http_referer': '/wp-admin/network/sites.php',
|
||||
}
|
||||
delete = self.get_base_url()
|
||||
delete = self.get_main_url()
|
||||
delete += '/wp-admin/network/sites.php?action=deleteblog'
|
||||
response = session.post(delete, data=data)
|
||||
self.validate_response(response)
|
||||
|
|
|
@ -22,12 +22,17 @@ class SaaSBaseForm(PluginDataForm):
|
|||
site_domain = self.instance.get_site_domain()
|
||||
else:
|
||||
site_domain = self.plugin.site_domain
|
||||
if site_domain:
|
||||
site_link = '<a href="http://%s">%s</a>' % (site_domain, site_domain)
|
||||
context = {
|
||||
'site_name': '<site_name>',
|
||||
'name': '<site_name>',
|
||||
}
|
||||
site_domain = site_domain % context
|
||||
if '<site_name>' in site_domain:
|
||||
site_link = site_domain
|
||||
else:
|
||||
site_link = '<site_name>.%s' % self.plugin.site_base_domain
|
||||
site_link = '<a href="http://%s">%s</a>' % (site_domain, site_domain)
|
||||
self.fields['site_url'].widget.display = site_link
|
||||
self.fields['name'].label = _("Site name") if self.plugin.site_base_domain else _("Username")
|
||||
self.fields['name'].label = _("Site name") if '%(' in self.plugin.site_domain else _("Username")
|
||||
|
||||
|
||||
class SaaSPasswordForm(SaaSBaseForm):
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
from .options import SoftwareService
|
||||
from .. import settings
|
||||
|
||||
|
||||
class DokuWikiService(SoftwareService):
|
||||
name = 'dokuwiki'
|
||||
verbose_name = "Dowkuwiki"
|
||||
icon = 'orchestra/icons/apps/Dokuwiki.png'
|
||||
|
||||
@property
|
||||
def site_base_domain(self):
|
||||
from .. import settings
|
||||
return settings.SAAS_DOKUWIKI_BASE_DOMAIN
|
||||
site_domain = settings.SAAS_DOKUWIKI_DOMAIN
|
||||
|
|
|
@ -13,7 +13,6 @@ from ..forms import SaaSPasswordForm
|
|||
class SoftwareService(plugins.Plugin):
|
||||
form = SaaSPasswordForm
|
||||
site_domain = None
|
||||
site_base_domain = None
|
||||
has_custom_domain = False
|
||||
icon = 'orchestra/icons/apps.png'
|
||||
class_verbose_name = _("Software as a Service")
|
||||
|
@ -32,9 +31,11 @@ class SoftwareService(plugins.Plugin):
|
|||
return fields + ('name',)
|
||||
|
||||
def get_site_domain(self):
|
||||
return self.site_domain or '.'.join(
|
||||
(self.instance.name, self.site_base_domain)
|
||||
)
|
||||
context = {
|
||||
'site_name': self.instance.name,
|
||||
'name': self.instance.name,
|
||||
}
|
||||
return self.site_domain % context
|
||||
|
||||
def clean_data(self):
|
||||
data = super(SoftwareService, self).clean_data()
|
||||
|
|
|
@ -29,8 +29,12 @@ class PHPListForm(SaaSPasswordForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(PHPListForm, self).__init__(*args, **kwargs)
|
||||
self.fields['name'].label = _("Site name")
|
||||
base_domain = self.plugin.site_base_domain
|
||||
help_text = _("Admin URL http://<site_name>.{}/admin/").format(base_domain)
|
||||
context = {
|
||||
'site_name': '<site_name>',
|
||||
'name': '<site_name>',
|
||||
}
|
||||
domain = self.plugin.site_domain % context
|
||||
help_text = _("Admin URL http://{}/admin/").format(domain)
|
||||
self.fields['site_url'].help_text = help_text
|
||||
|
||||
|
||||
|
@ -66,7 +70,7 @@ class PHPListService(SoftwareService):
|
|||
form = PHPListForm
|
||||
change_form = PHPListChangeForm
|
||||
icon = 'orchestra/icons/apps/Phplist.png'
|
||||
site_base_domain = settings.SAAS_PHPLIST_BASE_DOMAIN
|
||||
site_domain = settings.SAAS_PHPLIST_DOMAIN
|
||||
|
||||
def get_db_name(self):
|
||||
context = {
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from rest_framework import serializers
|
||||
|
||||
from .options import SoftwareService
|
||||
from .. import settings
|
||||
from ..forms import SaaSBaseForm
|
||||
|
||||
|
||||
|
@ -31,8 +32,4 @@ class WordPressService(SoftwareService):
|
|||
serializer = WordPressDataSerializer
|
||||
icon = 'orchestra/icons/apps/WordPress.png'
|
||||
change_readonly_fileds = ('email',)
|
||||
|
||||
@property
|
||||
def site_base_domain(self):
|
||||
from .. import settings
|
||||
return settings.SAAS_WORDPRESS_BASE_DOMAIN
|
||||
site_domain = settings.SAAS_WORDPRESS_DOMAIN
|
||||
|
|
|
@ -43,13 +43,13 @@ SAAS_WORDPRESS_ADMIN_PASSWORD = Setting('SAAS_WORDPRESS_ADMIN_PASSWORD',
|
|||
'secret'
|
||||
)
|
||||
|
||||
SAAS_WORDPRESS_BASE_URL = Setting('SAAS_WORDPRESS_BASE_URL',
|
||||
SAAS_WORDPRESS_MAIN_URL = Setting('SAAS_WORDPRESS_MAIN_URL',
|
||||
'https://blogs.{}/'.format(ORCHESTRA_BASE_DOMAIN),
|
||||
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default.",
|
||||
)
|
||||
|
||||
SAAS_WORDPRESS_BASE_DOMAIN = Setting('SAAS_WORDPRESS_BASE_DOMAIN',
|
||||
'blogs.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||
SAAS_WORDPRESS_DOMAIN = Setting('SAAS_WORDPRESS_DOMAIN',
|
||||
'%(site_name)s.blogs.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||
)
|
||||
|
||||
|
||||
|
@ -63,8 +63,8 @@ SAAS_DOKUWIKI_FARM_PATH = Setting('WEBSITES_DOKUWIKI_FARM_PATH',
|
|||
'/home/httpd/htdocs/wikifarm/farm'
|
||||
)
|
||||
|
||||
SAAS_DOKUWIKI_BASE_DOMAIN = Setting('SAAS_DOKUWIKI_BASE_DOMAIN',
|
||||
'dokuwiki.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||
SAAS_DOKUWIKI_DOMAIN = Setting('SAAS_DOKUWIKI_DOMAIN',
|
||||
'%(site_name)s.dokuwiki.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||
)
|
||||
|
||||
SAAS_DOKUWIKI_TEMPLATE_PATH = Setting('SAAS_DOKUWIKI_TEMPLATE_PATH',
|
||||
|
@ -125,8 +125,8 @@ SAAS_PHPLIST_BOUNCES_MAILBOX_PASSWORD = Setting('SAAS_PHPLIST_BOUNCES_MAILBOX_PA
|
|||
'secret',
|
||||
)
|
||||
|
||||
SAAS_PHPLIST_BASE_DOMAIN = Setting('SAAS_PHPLIST_BASE_DOMAIN',
|
||||
'lists.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||
SAAS_PHPLIST_DOMAIN = Setting('SAAS_PHPLIST_DOMAIN',
|
||||
'%(site_name)s.lists.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default.",
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue