Random fixes

This commit is contained in:
Marc Aymerich 2015-10-08 13:54:39 +00:00
parent e4edb89d2c
commit 38a46b5983
18 changed files with 305 additions and 149 deletions

View File

@ -423,4 +423,5 @@ mkhomedir_helper or create ssh homes with bash.rc and such
# account contacts inline, show provided fields and ignore the rest? # account contacts inline, show provided fields and ignore the rest?
# email usage -webkit-column-count:3;-moz-column-count:3;column-count:3; # email usage -webkit-column-count:3;-moz-column-count:3;column-count:3;
# Protect fucking search url and put into /admin/search and admin.py
# wordpressmu custom_url: set blog.domain

View File

@ -39,24 +39,56 @@ def get_urls():
admin.site.get_urls = get_urls admin.site.get_urls = get_urls
def get_model(model_name, model_name_map):
try:
return model_name_map[model_name.lower()]
except KeyError:
return
def search(request): def search(request):
query = request.GET.get('q', '') query = request.GET.get('q', '')
if query.endswith('!'): search_term = query
models = set()
selected_models = set()
model_name_map = {}
for service in itertools.chain(services, accounts):
if service.search:
models.add(service.model)
model_name_map[service.model._meta.model_name] = service.model
# Account direct access
if search_term.endswith('!'):
from ..contrib.accounts.models import Account from ..contrib.accounts.models import Account
# Account direct access search_term = search_term.replace('!', '')
query = query.replace('!', '')
try: try:
account = Account.objects.get(username=query) account = Account.objects.get(username=search_term)
except Account.DoesNotExist: except Account.DoesNotExist:
pass pass
else: else:
account_url = reverse('admin:accounts_account_change', args=(account.pk,)) account_url = reverse('admin:accounts_account_change', args=(account.pk,))
return redirect(account_url) return redirect(account_url)
# Search for specific model
elif ':' in search_term:
new_search_term = []
for part in search_term.split():
if ':' in part:
model_name, term = part.split(':')
model = get_model(model_name, model_name_map)
# Retry with singular version
if model is None and model_name.endswith('s'):
model = get_model(model_name[:-1], model_name_map)
if model is None:
new_search_term.append(':'.join((model_name, term)))
else:
selected_models.add(model)
new_search_term.append(term)
else:
new_search_term.append(part)
search_term = ' '.join(new_search_term)
if selected_models:
models = selected_models
results = OrderedDict() results = OrderedDict()
models = set()
for service in itertools.chain(services, accounts):
if service.search:
models.add(service.model)
models = sorted(models, key=lambda m: m._meta.verbose_name_plural.lower()) models = sorted(models, key=lambda m: m._meta.verbose_name_plural.lower())
total = 0 total = 0
for model in models: for model in models:
@ -66,7 +98,7 @@ def search(request):
pass pass
else: else:
qs = modeladmin.get_queryset(request) qs = modeladmin.get_queryset(request)
qs, search_use_distinct = modeladmin.get_search_results(request, qs, query) qs, search_use_distinct = modeladmin.get_search_results(request, qs, search_term)
if search_use_distinct: if search_use_distinct:
qs = qs.distinct() qs = qs.distinct()
num = len(qs) num = len(qs)
@ -79,6 +111,7 @@ def search(request):
'total': total, 'total': total,
'columns': min(int(total/17), 3), 'columns': min(int(total/17), 3),
'query': query, 'query': query,
'search_term': search_term,
'results': results, 'results': results,
'search_autofocus': True, 'search_autofocus': True,
} }

View File

@ -80,17 +80,24 @@ class EnhaceSearchMixin(object):
def get_search_results(self, request, queryset, search_term): def get_search_results(self, request, queryset, search_term):
""" allows to specify field <field_name>:<search_term> """ """ allows to specify field <field_name>:<search_term> """
search_fields = self.get_search_fields(request) search_fields = self.get_search_fields(request)
if ':' in search_term: if '=' in search_term:
fields = {field.split('__')[0]: field for field in search_fields} fields = {field.split('__')[0]: field for field in search_fields}
new_search_term = [] new_search_term = []
for part in search_term.split(): for part in search_term.split():
cur_search_term = '' field = None
for field, term in pairwise(part.split(':')): if '=' in part:
field, term = part.split('=')
kwarg = '%s__icontains'
c_term = term
if term.startswith(('"', "'")) and term.endswith(('"', "'")):
c_term = term[1:-1]
kwarg = '%s__iexact'
if field in fields: if field in fields:
queryset = queryset.filter(**{'%s__icontains' % fields[field]: term}) queryset = queryset.filter(**{kwarg % fields[field]: c_term})
else: else:
cur_search_term += ':'.join((field, term)) new_search_term.append('='.join((field, term)))
new_search_term.append(cur_search_term) else:
new_search_term.append(part)
search_term = ' '.join(new_search_term) search_term = ' '.join(new_search_term)
return super(EnhaceSearchMixin, self).get_search_results(request, queryset, search_term) return super(EnhaceSearchMixin, self).get_search_results(request, queryset, search_term)

View File

@ -538,7 +538,7 @@ msgstr ""
#: templates/bills/microspective.html:149 #: templates/bills/microspective.html:149
msgid "QUESTIONS" msgid "QUESTIONS"
msgstr "" msgstr "PREGUNTAS"
#: templates/bills/microspective.html:150 #: templates/bills/microspective.html:150
#, python-format #, python-format

View File

@ -330,7 +330,7 @@ class Bill(models.Model):
subtotals[tax] = total subtotals[tax] = total
result = {} result = {}
for tax, subtotal in subtotals.items(): for tax, subtotal in subtotals.items():
result[tax] = (subtotal, round(tax/100*subtotal, 2)) result[tax] = [subtotal, round(tax/100*subtotal, 2)]
return result return result
@lru_cache() @lru_cache()

View File

@ -127,13 +127,30 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
if not add: if not add:
self.check_unrelated_address(request, obj) self.check_unrelated_address(request, obj)
self.check_matching_address(request, obj)
return super(MailboxAdmin, self).render_change_form( return super(MailboxAdmin, self).render_change_form(
request, context, add, change, form_url, obj) request, context, add, change, form_url, obj)
def log_addition(self, request, object): def log_addition(self, request, object):
self.check_unrelated_address(request, object) self.check_unrelated_address(request, object)
self.check_matching_address(request, object)
return super(MailboxAdmin, self).log_addition(request, object) return super(MailboxAdmin, self).log_addition(request, object)
def check_matching_address(self, request, obj):
local_domain = settings.MAILBOXES_LOCAL_DOMAIN
if obj.name and local_domain:
try:
addr = Address.objects.get(
name=obj.name, domain__name=local_domain, account_id=self.account.pk)
except Address.DoesNotExist:
pass
else:
if addr not in obj.addresses.all():
msg = _("Mailbox '%s' local address matches '%s', please consider if "
"selecting it makes sense.") % (obj, addr)
if msg not in (m.message for m in messages.get_messages(request)):
self.message_user(request, msg, level=messages.WARNING)
def check_unrelated_address(self, request, obj): def check_unrelated_address(self, request, obj):
# Check if there exists an unrelated local Address for this mbox # Check if there exists an unrelated local Address for this mbox
local_domain = settings.MAILBOXES_LOCAL_DOMAIN local_domain = settings.MAILBOXES_LOCAL_DOMAIN
@ -169,7 +186,9 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
fields = ('account_link', 'email_link', 'mailboxes', 'forward') fields = ('account_link', 'email_link', 'mailboxes', 'forward')
add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward') add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
# inlines = [AutoresponseInline] # inlines = [AutoresponseInline]
search_fields = ('forward', 'mailboxes__name', 'account__username', 'computed_email') search_fields = (
'forward', 'mailboxes__name', 'account__username', 'computed_email', 'domain__name'
)
readonly_fields = ('account_link', 'domain_link', 'email_link') readonly_fields = ('account_link', 'domain_link', 'email_link')
actions = (SendAddressEmail(),) actions = (SendAddressEmail(),)
filter_by_account_fields = ('domain', 'mailboxes') filter_by_account_fields = ('domain', 'mailboxes')
@ -223,6 +242,29 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
def get_queryset(self, request): def get_queryset(self, request):
qs = super(AddressAdmin, self).get_queryset(request) qs = super(AddressAdmin, self).get_queryset(request)
return qs.annotate(computed_email=Concat(F('name'), V('@'), F('domain__name'))) return qs.annotate(computed_email=Concat(F('name'), V('@'), F('domain__name')))
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
if not add:
self.check_matching_mailbox(request, obj)
return super(AddressAdmin, self).render_change_form(
request, context, add, change, form_url, obj)
def log_addition(self, request, object):
self.check_matching_mailbox(request, object)
return super(AddressAdmin, self).log_addition(request, object)
def check_matching_mailbox(self, request, obj):
# Check if new addresse matches with a mbox because of having a local domain
if obj.name and obj.domain and obj.domain.name == settings.MAILBOXES_LOCAL_DOMAIN:
if obj.name not in obj.forward.split() and Mailbox.objects.filter(name=obj.name).exists():
for mailbox in obj.mailboxes.all():
if mailbox.name == obj.name:
return
msg = _("Address '%s' matches mailbox '%s' local address, please consider "
"if makes sense adding the mailbox on the mailboxes or forward field."
) % (obj, obj.name)
if msg not in (m.message for m in messages.get_messages(request)):
self.message_user(request, msg, level=messages.WARNING)
admin.site.register(Mailbox, MailboxAdmin) admin.site.register(Mailbox, MailboxAdmin)

View File

@ -44,24 +44,6 @@ class MailboxForm(forms.ModelForm):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.fields['addresses'].initial = self.instance.addresses.all() self.fields['addresses'].initial = self.instance.addresses.all()
def clean(self):
cleaned_data = super(MailboxForm, self).clean()
name = self.instance.name if self.instance.pk else cleaned_data.get('name')
local_domain = settings.MAILBOXES_LOCAL_DOMAIN
if name and local_domain:
try:
addr = Address.objects.get(
name=name, domain__name=local_domain, account_id=self.modeladmin.account.pk)
except Address.DoesNotExist:
pass
else:
if addr not in cleaned_data.get('addresses', []):
raise ValidationError({
'addresses': _("This mailbox local address matche '%s', "
"please make explicit this fact by selecting it.") % addr
})
return cleaned_data
class MailboxChangeForm(UserChangeForm, MailboxForm): class MailboxChangeForm(UserChangeForm, MailboxForm):
@ -86,20 +68,3 @@ class AddressForm(forms.ModelForm):
forward = cleaned_data.get('forward', '') forward = cleaned_data.get('forward', '')
if not cleaned_data.get('mailboxes', True) and not forward: if not cleaned_data.get('mailboxes', True) and not forward:
raise ValidationError(_("Mailboxes or forward address should be provided.")) raise ValidationError(_("Mailboxes or forward address should be provided."))
# Check if new addresse matches with a mbox because of having a local domain
if self.instance.pk:
name = self.instance.name
domain = self.instance.domain
else:
name = cleaned_data.get('name')
domain = cleaned_data.get('domain')
if domain and name and domain.name == settings.MAILBOXES_LOCAL_DOMAIN:
if name not in forward.split() and Mailbox.objects.filter(name=name).exists():
for mailbox in cleaned_data.get('mailboxes', []):
if mailbox.name == name:
return
raise ValidationError(
_("This address matches mailbox '%s' local address, please make explicit "
"this fact by adding the mailbox on the mailboxes or forward field.") % name
)
return cleaned_data

View File

@ -103,7 +103,7 @@ def OpenSSH(backend, log, server, cmds, async=False):
script = '\n'.join(cmds) script = '\n'.join(cmds)
script = script.replace('\r', '') script = script.replace('\r', '')
log.state = log.STARTED log.state = log.STARTED
log.script = script log.script = '\n'.join((log.script, script))
log.save(update_fields=('script', 'state', 'updated_at')) log.save(update_fields=('script', 'state', 'updated_at'))
if not cmds: if not cmds:
return return
@ -116,12 +116,14 @@ def OpenSSH(backend, log, server, cmds, async=False):
log.stdout += state.stdout.decode('utf8') log.stdout += state.stdout.decode('utf8')
log.stderr += state.stderr.decode('utf8') log.stderr += state.stderr.decode('utf8')
log.save(update_fields=('stdout', 'stderr', 'updated_at')) log.save(update_fields=('stdout', 'stderr', 'updated_at'))
log.exit_code = state.exit_code exit_code = state.exit_code
else: else:
log.stdout = ssh.stdout.decode('utf8') log.stdout += ssh.stdout.decode('utf8')
log.stderr = ssh.stderr.decode('utf8') log.stderr += ssh.stderr.decode('utf8')
log.exit_code = ssh.exit_code exit_code = ssh.exit_code
log.state = log.SUCCESS if log.exit_code == 0 else log.FAILURE if not log.exit_code:
log.exit_code = exit_code
log.state = log.SUCCESS if exit_code == 0 else log.FAILURE
logger.debug('%s execution state on %s is %s' % (backend, server, log.state)) logger.debug('%s execution state on %s is %s' % (backend, server, log.state))
log.save() log.save()
except: except:
@ -164,11 +166,12 @@ def Python(backend, log, server, cmds, async=False):
except: except:
log.exit_code = 1 log.exit_code = 1
log.state = log.FAILURE log.state = log.FAILURE
log.stdout = '\n'.join(stdout) log.stdout += '\n'.join(stdout)
log.traceback = ExceptionInfo(sys.exc_info()).traceback log.traceback += ExceptionInfo(sys.exc_info()).traceback
logger.error('Exception while executing %s on %s' % (backend, server)) logger.error('Exception while executing %s on %s' % (backend, server))
else: else:
log.exit_code = 0 if not log.exit_code:
log.state = log.SUCCESS log.exit_code = 0
log.state = log.SUCCESS
logger.debug('%s execution state on %s is %s' % (backend, server, log.state)) logger.debug('%s execution state on %s is %s' % (backend, server, log.state))
log.save() log.save()

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-07-15 12:08+0000\n" "POT-Creation-Date: 2015-10-08 11:53+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -52,7 +52,7 @@ msgstr ""
msgid "Process" msgid "Process"
msgstr "" msgstr ""
#: actions.py:63 actions.py:134 models.py:97 models.py:164 #: actions.py:63 actions.py:134 models.py:97 models.py:172
msgid "Executed" msgid "Executed"
msgstr "" msgstr ""
@ -112,7 +112,7 @@ msgstr ""
msgid "%s selected processes have been marked as executed." msgid "%s selected processes have been marked as executed."
msgstr "" msgstr ""
#: actions.py:150 models.py:165 #: actions.py:150 models.py:173
msgid "Aborted" msgid "Aborted"
msgstr "" msgstr ""
@ -133,15 +133,19 @@ msgstr ""
msgid "Commit" msgid "Commit"
msgstr "" msgstr ""
#: admin.py:43 #: admin.py:44
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: admin.py:105 #: admin.py:106
msgid "proc" msgid "proc"
msgstr "" msgstr ""
#: admin.py:158 #: admin.py:129 templates/admin/payments/transaction/report.html:62
msgid "State"
msgstr ""
#: admin.py:168
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
@ -186,6 +190,18 @@ msgstr ""
msgid "SEPA Direct Debit" msgid "SEPA Direct Debit"
msgstr "" msgstr ""
#: methods/sepadirectdebit.py:47
msgid ""
"The transaction is created and requires the generation of the SEPA direct "
"debit XML file."
msgstr ""
#: methods/sepadirectdebit.py:49
msgid ""
"SEPA Direct Debit XML file is generated but needs to be sent to the "
"financial institution."
msgstr ""
#: models.py:20 #: models.py:20
msgid "account" msgid "account"
msgstr "" msgstr ""
@ -194,7 +210,7 @@ msgstr ""
msgid "method" msgid "method"
msgstr "" msgstr ""
#: models.py:24 models.py:169 #: models.py:24 models.py:177
msgid "data" msgid "data"
msgstr "" msgstr ""
@ -211,68 +227,90 @@ msgid "Waitting execution"
msgstr "" msgstr ""
#: models.py:102 #: models.py:102
msgid "bill" msgid ""
"The transaction is created and requires processing by the specific payment "
"method."
msgstr "" msgstr ""
#: models.py:105 #: models.py:104
msgid "source" msgid ""
"The transaction is processed and its pending execution on the related "
"financial institution."
msgstr ""
#: models.py:106
msgid "The transaction is executed on the financial institution."
msgstr "" msgstr ""
#: models.py:107 #: models.py:107
msgid "The transaction ammount is secured."
msgstr ""
#: models.py:108
msgid ""
"The transaction has failed and the ammount is lost, a new transaction should "
"be created for recharging."
msgstr ""
#: models.py:112
msgid "bill"
msgstr ""
#: models.py:115
msgid "source"
msgstr ""
#: models.py:117
msgid "process" msgid "process"
msgstr "" msgstr ""
#: models.py:108 models.py:171 #: models.py:118 models.py:179
msgid "state" msgid "state"
msgstr "" msgstr ""
#: models.py:110 #: models.py:120
msgid "amount" msgid "amount"
msgstr "" msgstr ""
#: models.py:112 models.py:172 #: models.py:122 models.py:180
msgid "created" msgid "created"
msgstr "" msgstr ""
#: models.py:113 #: models.py:123
msgid "modified" msgid "modified"
msgstr "" msgstr ""
#: models.py:128 #: models.py:138
msgid "New transactions can not be allocated for this bill." msgid "New transactions can not be allocated for this bill."
msgstr "" msgstr ""
#: models.py:163 templates/admin/payments/transaction/report.html:63 #: models.py:171 templates/admin/payments/transaction/report.html:63
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#: models.py:166 #: models.py:174
msgid "Commited" msgid "Commited"
msgstr "" msgstr ""
#: models.py:170 #: models.py:178
msgid "file" msgid "file"
msgstr "" msgstr ""
#: models.py:173 #: models.py:181
msgid "updated" msgid "updated"
msgstr "" msgstr ""
#: models.py:176 #: models.py:184
msgid "Transaction processes" msgid "Transaction processes"
msgstr "" msgstr ""
#: settings.py:12 #: settings.py:14
#, fuzzy, python-format
#| msgid ""
#| "This bill will be automatically charged to your bank account with IBAN "
#| "number<br><strong>%s</strong>."
msgid "" msgid ""
"<strong>Direct debit</strong>, this bill will be automatically charged to " "<strong>Direct debit</strong>, this bill will be automatically charged to "
"your bank account with IBAN number<br><strong>%(number)s</strong>." "your bank account with IBAN number<br><strong>%(number)s</strong>."
msgstr "" msgstr ""
"<strong>Càrrec per domiciliació</strong>, aquesta factura es cobrarà " "<strong>Càrrec per domiciliació</strong>, aquesta factura es cobrarà "
"automaticament en el teu compte bancari amb IBAN <br><strong>%s</strong>." "automaticament en el teu compte bancari amb IBAN <br><strong>%(number)s</strong>."
#: templates/admin/payments/transaction/report.html:38 #: templates/admin/payments/transaction/report.html:38
msgid "Summary" msgid "Summary"
@ -299,10 +337,6 @@ msgstr ""
msgid "Contact" msgid "Contact"
msgstr "" msgstr ""
#: templates/admin/payments/transaction/report.html:62
msgid "State"
msgstr ""
#: templates/admin/payments/transaction/report.html:64 #: templates/admin/payments/transaction/report.html:64
msgid "Updated" msgid "Updated"
msgstr "" msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-07-15 12:08+0000\n" "POT-Creation-Date: 2015-10-08 12:14+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -52,7 +52,7 @@ msgstr ""
msgid "Process" msgid "Process"
msgstr "" msgstr ""
#: actions.py:63 actions.py:134 models.py:97 models.py:164 #: actions.py:63 actions.py:134 models.py:97 models.py:172
msgid "Executed" msgid "Executed"
msgstr "" msgstr ""
@ -112,7 +112,7 @@ msgstr ""
msgid "%s selected processes have been marked as executed." msgid "%s selected processes have been marked as executed."
msgstr "" msgstr ""
#: actions.py:150 models.py:165 #: actions.py:150 models.py:173
msgid "Aborted" msgid "Aborted"
msgstr "" msgstr ""
@ -133,15 +133,19 @@ msgstr ""
msgid "Commit" msgid "Commit"
msgstr "" msgstr ""
#: admin.py:43 #: admin.py:44
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: admin.py:105 #: admin.py:106
msgid "proc" msgid "proc"
msgstr "" msgstr ""
#: admin.py:158 #: admin.py:129 templates/admin/payments/transaction/report.html:62
msgid "State"
msgstr ""
#: admin.py:168
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
@ -186,6 +190,18 @@ msgstr ""
msgid "SEPA Direct Debit" msgid "SEPA Direct Debit"
msgstr "" msgstr ""
#: methods/sepadirectdebit.py:47
msgid ""
"The transaction is created and requires the generation of the SEPA direct "
"debit XML file."
msgstr ""
#: methods/sepadirectdebit.py:49
msgid ""
"SEPA Direct Debit XML file is generated but needs to be sent to the "
"financial institution."
msgstr ""
#: models.py:20 #: models.py:20
msgid "account" msgid "account"
msgstr "" msgstr ""
@ -194,7 +210,7 @@ msgstr ""
msgid "method" msgid "method"
msgstr "" msgstr ""
#: models.py:24 models.py:169 #: models.py:24 models.py:177
msgid "data" msgid "data"
msgstr "" msgstr ""
@ -211,68 +227,90 @@ msgid "Waitting execution"
msgstr "" msgstr ""
#: models.py:102 #: models.py:102
msgid "bill" msgid ""
"The transaction is created and requires processing by the specific payment "
"method."
msgstr "" msgstr ""
#: models.py:105 #: models.py:104
msgid "source" msgid ""
"The transaction is processed and its pending execution on the related "
"financial institution."
msgstr ""
#: models.py:106
msgid "The transaction is executed on the financial institution."
msgstr "" msgstr ""
#: models.py:107 #: models.py:107
msgid "The transaction ammount is secured."
msgstr ""
#: models.py:108
msgid ""
"The transaction has failed and the ammount is lost, a new transaction should "
"be created for recharging."
msgstr ""
#: models.py:112
msgid "bill"
msgstr ""
#: models.py:115
msgid "source"
msgstr ""
#: models.py:117
msgid "process" msgid "process"
msgstr "" msgstr ""
#: models.py:108 models.py:171 #: models.py:118 models.py:179
msgid "state" msgid "state"
msgstr "" msgstr ""
#: models.py:110 #: models.py:120
msgid "amount" msgid "amount"
msgstr "" msgstr ""
#: models.py:112 models.py:172 #: models.py:122 models.py:180
msgid "created" msgid "created"
msgstr "" msgstr ""
#: models.py:113 #: models.py:123
msgid "modified" msgid "modified"
msgstr "" msgstr ""
#: models.py:128 #: models.py:138
msgid "New transactions can not be allocated for this bill." msgid "New transactions can not be allocated for this bill."
msgstr "" msgstr ""
#: models.py:163 templates/admin/payments/transaction/report.html:63 #: models.py:171 templates/admin/payments/transaction/report.html:63
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#: models.py:166 #: models.py:174
msgid "Commited" msgid "Commited"
msgstr "" msgstr ""
#: models.py:170 #: models.py:178
msgid "file" msgid "file"
msgstr "" msgstr ""
#: models.py:173 #: models.py:181
msgid "updated" msgid "updated"
msgstr "" msgstr ""
#: models.py:176 #: models.py:184
msgid "Transaction processes" msgid "Transaction processes"
msgstr "" msgstr ""
#: settings.py:12 #: settings.py:14
#, fuzzy, python-format
#| msgid ""
#| "This bill will be automatically charged to your bank account with IBAN "
#| "number<br><strong>%s</strong>."
msgid "" msgid ""
"<strong>Direct debit</strong>, this bill will be automatically charged to " "<strong>Direct debit</strong>, this bill will be automatically charged to "
"your bank account with IBAN number<br><strong>%(number)s</strong>." "your bank account with IBAN number<br><strong>%(number)s</strong>."
msgstr "" msgstr ""
"<strong>Adeudo por domiciliación</strong>, esta factura se cobrará " "<strong>Adeudo por domiciliación</strong>, esta factura se cobrará "
"automaticamente en tu cuenta bancaria con IBAN <br><strong>%s</strong>." "automaticamente en tu cuenta bancaria con IBAN <br><strong>%(number)s</strong>."
#: templates/admin/payments/transaction/report.html:38 #: templates/admin/payments/transaction/report.html:38
msgid "Summary" msgid "Summary"
@ -299,10 +337,6 @@ msgstr ""
msgid "Contact" msgid "Contact"
msgstr "" msgstr ""
#: templates/admin/payments/transaction/report.html:62
msgid "State"
msgstr ""
#: templates/admin/payments/transaction/report.html:64 #: templates/admin/payments/transaction/report.html:64
msgid "Updated" msgid "Updated"
msgstr "" msgstr ""

View File

@ -1,4 +1,5 @@
import re import re
import sys
import textwrap import textwrap
from urllib.parse import urlparse from urllib.parse import urlparse
@ -46,6 +47,7 @@ class WordpressMuBackend(ServiceController):
raise RuntimeError(errors[0] if errors else 'Unknown %i error' % response.status_code) raise RuntimeError(errors[0] if errors else 'Unknown %i error' % response.status_code)
def get_id(self, session, saas): def get_id(self, session, saas):
blog_id = saas.data.get('blog_id')
search = self.get_main_url() search = self.get_main_url()
search += '/wp-admin/network/sites.php?s=%s&action=blogs' % saas.name search += '/wp-admin/network/sites.php?s=%s&action=blogs' % saas.name
regex = re.compile( regex = re.compile(
@ -55,23 +57,31 @@ class WordpressMuBackend(ServiceController):
content = session.get(search).content.decode('utf8') content = session.get(search).content.decode('utf8')
# Get id # Get id
ids = regex.search(content) ids = regex.search(content)
if not ids: if not ids and not blog_id:
raise RuntimeError("Blog '%s' not found" % saas.name) raise RuntimeError("Blog '%s' not found" % saas.name)
ids = ids.groups() if ids:
if len(ids) > 1: ids = ids.groups()
raise ValueError("Multiple matches") if len(ids) > 1 and not blog_id:
raise ValueError("Multiple matches")
# Get wpnonce # Get wpnonce
wpnonce = re.search(r'<span class="delete">(.*)</span>', content).groups()[0] try:
wpnonce = re.search(r'<span class="delete">(.*)</span>', content).groups()[0]
except TypeError:
# No search results, try some luck
wpnonce = content
wpnonce = re.search(r'_wpnonce=([^"]*)"', wpnonce).groups()[0] wpnonce = re.search(r'_wpnonce=([^"]*)"', wpnonce).groups()[0]
return int(ids[0]), wpnonce return blog_id or int(ids[0]), wpnonce
def create_blog(self, saas, server): def create_blog(self, saas, server):
if saas.data.get('blog_id'):
return
session = requests.Session() session = requests.Session()
self.login(session) self.login(session)
# Check if blog already exists # Check if blog already exists
try: try:
self.get_id(session, saas) blog_id, wpnonce = self.get_id(session, saas)
except RuntimeError: except RuntimeError:
url = self.get_main_url() url = self.get_main_url()
url += '/wp-admin/network/site-new.php' url += '/wp-admin/network/site-new.php'
@ -91,6 +101,16 @@ class WordpressMuBackend(ServiceController):
# Validate response # Validate response
response = session.post(url, data=data) response = session.post(url, data=data)
self.validate_response(response) self.validate_response(response)
blog_id = re.compile(r'<link id="wp-admin-canonical" rel="canonical" href="http(?:[^ ]+)/wp-admin/network/site-new.php\?id=([0-9]+)" />')
content = response.content.decode('utf8')
blog_id = blog_id.search(content).groups()[0]
sys.stdout.write("Created blog ID: %s\n" % blog_id)
saas.data['blog_id'] = int(blog_id)
saas.save(update_fields=('data',))
else:
sys.stdout.write("Retrieved blog ID: %s\n" % blog_id)
saas.data['blog_id'] = int(blog_id)
saas.save(update_fields=('data',))
def delete_blog(self, saas, server): def delete_blog(self, saas, server):
session = requests.Session() session = requests.Session()
@ -122,32 +142,37 @@ class WordpressMuBackend(ServiceController):
def save(self, saas): def save(self, saas):
self.append(self.create_blog, saas) self.append(self.create_blog, saas)
context = self.get_context(saas) context = self.get_context(saas)
context['IDENT'] = "b.domain = '%(domain)s'" % context
if context['blog_id']:
context['IDENT'] = "b.blog_id = '%(blog_id)s'" % context
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
# Update custom URL mapping # Update custom URL mapping
existing=( $(mysql -Nrs %(db_name)s --execute=' existing=( $(mysql -Nrs %(db_name)s --execute="
SELECT b.blog_id, b.domain, m.domain, b.path SELECT b.blog_id, b.domain, m.domain, b.path
FROM wp_domain_mapping AS m, wp_blogs AS b FROM wp_domain_mapping AS m, wp_blogs AS b
WHERE m.blog_id = b.blog_id AND m.active AND b.domain = "%(domain)s";') ) WHERE m.blog_id = b.blog_id AND m.active AND %(IDENT)s;") )
if [[ ${existing[0]} != '' ]]; then if [[ ${existing[0]} != '' ]]; then
# Clear custom domain
if [[ "%(custom_domain)s" == "" ]]; then if [[ "%(custom_domain)s" == "" ]]; then
mysql %(db_name)s --execute=" mysql %(db_name)s --execute="
DELETE wp_domain_mapping AS m, wp_blogs AS b DELETE FROM m
WHERE m.blog_id = b.blog_id AND m.active AND b.domain = '%(domain)s'; USING wp_domain_mapping AS m, wp_blogs AS b
WHERE m.blog_id = b.blog_id AND m.active AND %(IDENT)s';
UPDATE wp_blogs UPDATE wp_blogs
SET path='/' SET path='/'
WHERE blog_id=${existing[0]};" WHERE blog_id=${existing[0]};"
elif [[ "${existing[2]}" != "%(custom_domain)s" || "${existing[3]}" != "%(custom_path)s" ]]; then elif [[ "${existing[2]}" != "%(custom_domain)s" || "${existing[3]}" != "%(custom_path)s" ]]; then
mysql %(db_name)s --execute=' mysql %(db_name)s --execute="
UPDATE wp_domain_mapping as m, wp_blogs as b UPDATE wp_domain_mapping as m, wp_blogs as b
SET m.domain = "%(custom_domain)s", b.path = "%(custom_path)s" SET m.domain = '%(custom_domain)s', b.path = '%(custom_path)s'
WHERE m.blog_id = b.blog_id AND m.active AND b.domain = "%(domain)s";' WHERE m.blog_id = b.blog_id AND m.active AND %(IDENT)s';"
fi fi
else elif [[ "%(custom_domain)s" != "" ]]; then
blog=( $(mysql -Nrs %(db_name)s --execute=' blog=( $(mysql -Nrs %(db_name)s --execute="
SELECT blog_id, path FROM wp_blogs WHERE domain = "%(domain)s";') ) SELECT blog_id, path FROM wp_blogs WHERE domain = '%(domain)s';") )
mysql %(db_name)s --execute=' mysql %(db_name)s --execute="
INSERT INTO wp_domain_mapping INSERT INTO wp_domain_mapping
VALUES (blog_id, domain, active) ($blog_id, "%(custom_domain)s", 1);' (blog_id, domain, active) VALUES (${blog[0]}, '%(custom_domain)s', 1);"
if [[ "${blog[1]}" != "%(custom_path)s" ]]; then if [[ "${blog[1]}" != "%(custom_path)s" ]]; then
mysql %(db_name)s --execute=" mysql %(db_name)s --execute="
UPDATE wp_blogs UPDATE wp_blogs
@ -165,6 +190,9 @@ class WordpressMuBackend(ServiceController):
context = { context = {
'db_name': settings.SAAS_WORDPRESS_DB_NAME, 'db_name': settings.SAAS_WORDPRESS_DB_NAME,
'domain': domain, 'domain': domain,
'custom_domain': '',
'custom_path': '/',
'blog_id': saas.data.get('blog_id', ''),
} }
if saas.custom_url: if saas.custom_url:
custom_url = urlparse(saas.custom_url) custom_url = urlparse(saas.custom_url)

View File

@ -113,12 +113,14 @@ class SoftwareService(plugins.Plugin):
return helpers.create_or_update_directive(self) return helpers.create_or_update_directive(self)
def delete_directive(self): def delete_directive(self):
directive = None
try: try:
old = type(self.instance).objects.get(pk=self.instance.pk) old = type(self.instance).objects.get(pk=self.instance.pk)
directive = self.get_directive(old) if old.custom_url:
directive = self.get_directive(old)
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass return
else: if directive is not None:
directive.delete() directive.delete()
def save(self): def save(self):

View File

@ -3,6 +3,8 @@ from django.utils.safestring import mark_safe
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.forms import widgets
from .options import SoftwareService from .options import SoftwareService
from .. import settings from .. import settings
from ..forms import SaaSBaseForm from ..forms import SaaSBaseForm
@ -12,6 +14,8 @@ class WordPressForm(SaaSBaseForm):
email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}), email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}),
help_text=_("A new user will be created if the above email address is not in the database.<br>" help_text=_("A new user will be created if the above email address is not in the database.<br>"
"The username and password will be mailed to this email address.")) "The username and password will be mailed to this email address."))
blog_id = forms.IntegerField(label=("Blog ID"), widget=widgets.SpanWidget, required=False,
help_text=_("ID of this user on the GitLab server, the only attribute that not changes."))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(WordPressForm, self).__init__(*args, **kwargs) super(WordPressForm, self).__init__(*args, **kwargs)
@ -23,6 +27,7 @@ class WordPressForm(SaaSBaseForm):
class WordPressDataSerializer(serializers.Serializer): class WordPressDataSerializer(serializers.Serializer):
email = serializers.EmailField(label=_("Email")) email = serializers.EmailField(label=_("Email"))
blog_id = serializers.IntegerField(label=_("Blog ID"), required=False)
class WordPressService(SoftwareService): class WordPressService(SoftwareService):
@ -31,6 +36,6 @@ class WordPressService(SoftwareService):
form = WordPressForm form = WordPressForm
serializer = WordPressDataSerializer serializer = WordPressDataSerializer
icon = 'orchestra/icons/apps/WordPress.png' icon = 'orchestra/icons/apps/WordPress.png'
change_readonly_fileds = ('email',) change_readonly_fileds = ('email', 'blog_id')
site_domain = settings.SAAS_WORDPRESS_DOMAIN site_domain = settings.SAAS_WORDPRESS_DOMAIN
allow_custom_url = settings.SAAS_WORDPRESS_ALLOW_CUSTOM_URL allow_custom_url = settings.SAAS_WORDPRESS_ALLOW_CUSTOM_URL

View File

@ -53,7 +53,9 @@
<input type="text" id="searchbox" style="margin-left:15px;margin-top:7px;" name="q" <input type="text" id="searchbox" style="margin-left:15px;margin-top:7px;" name="q"
placeholder="Search" size="25" value="{{ query }}" placeholder="Search" size="25" value="{{ query }}"
{% if search_autofocus or app_list %}autofocus="autofocus"{% endif %} {% if search_autofocus or app_list %}autofocus="autofocus"{% endif %}
title="Use 'username!' for account direct access."> title="Use 'accountname!' for account direct access
Use 'service:word' for searching on specific services
Use 'fieldname=word' for searching on specific fields">
</form> </form>
<span style="float:right;color:grey;margin:10px;font-size:11px;"> <span style="float:right;color:grey;margin:10px;font-size:11px;">
{% url 'admin:accounts_account_change' user.pk as user_change_url %} {% url 'admin:accounts_account_change' user.pk as user_change_url %}

View File

@ -7,7 +7,7 @@
<div> <div>
<div style="margin:20px;-webkit-column-count:{{ columns }};-moz-column-count:{{ columns }};column-count:{{ columns }};"> <div style="margin:20px;-webkit-column-count:{{ columns }};-moz-column-count:{{ columns }};column-count:{{ columns }};">
{% for opts, qs in results.items %} {% for opts, qs in results.items %}
<h3><a href="{% url opts|admin_urlname:'changelist' %}?q={{ query }}">{{ opts.verbose_name_plural|capfirst }}</a> <h3><a href="{% url opts|admin_urlname:'changelist' %}?q={{ search_term }}">{{ opts.verbose_name_plural|capfirst }}</a>
<span style="font-size:11px"> {{ qs|length }} results</span></h3> <span style="font-size:11px"> {{ qs|length }} results</span></h3>
<ul> <ul>
{% for instance in qs %} {% for instance in qs %}