Compare commits

..

95 Commits

Author SHA1 Message Date
Jorge Pastor 0e0a4d1e6b musician domains template fix path saas-list to saas-wp-list 2024-11-11 10:39:49 +01:00
Jorge Pastor 5462abd0d7 musician saas traductions 2024-11-08 12:38:26 +01:00
Jorge Pastor c5c7d2d6d3 musician dashboard nextcloud new filter only resources nextcloud 2024-11-07 18:29:17 +01:00
Jorge Pastor 51f61b2a30 musician saas nextcloud/wordpress v1 2024-11-07 18:05:47 +01:00
Jorge Pastor 9f8e6ce9b8 musician reorder code saas 2024-11-07 17:15:47 +01:00
Jorge Pastor e5c046c3af musician saas nextcloud/WP separate views 2024-11-07 13:18:38 +01:00
Jorge Pastor 228ee30d6e musician saas nextcloud/WP initial 2024-11-07 11:44:15 +01:00
Jorge Pastor ecd273bcc3 musician saas wordpress initial 2024-10-30 13:29:34 +01:00
Jorge Pastor b346d5403b musician saas edit/delete only nextcloud 2024-10-29 19:14:43 +01:00
Jorge Pastor 4879eec69e musician dashboard bar % in individual resources 2024-10-11 11:38:54 +02:00
Jorge Pastor bee347c9f5 orchestra list exec postmap after edit mailman3_virt* 2024-10-03 12:35:01 +02:00
Jorge Pastor 5b6bc35063 musician cursor-pointer 2024-09-12 13:27:48 +02:00
Jorge Pastor c6b5cb1d23 orchestra traductions mas 2024-09-12 12:42:43 +02:00
Jorge Pastor 599b3115dc orchestra traductions 2024-09-06 12:59:28 +02:00
Jorge Pastor ff444bdb32 orchestra traductions 2024-09-06 12:58:49 +02:00
Jorge Pastor 26497b95b1 musician traductions 2024-09-04 14:08:04 +02:00
Jorge Pastor ace8af24d5 webapps php backend: check file sock was create 2024-08-30 13:08:13 +02:00
Jorge Pastor 33293d2ba8 musicias show_history 2024-08-20 10:46:04 +02:00
Jorge Pastor 20bcb83c83 musicias show_history 2024-08-14 13:04:26 +02:00
Jorge Pastor 14ff506a0d musicias show_history basic 2024-08-09 13:21:10 +02:00
Jorge Pastor 4ff5abe8f6 musicias show_history basic 2024-08-09 13:21:01 +02:00
Jorge Pastor 081b094a3d mailboxes fix enable and disable user 2024-08-07 11:13:19 +02:00
Jorge Pastor b11ebc5d99 musician traductions ca, es + 2024-08-06 13:02:26 +02:00
Jorge Pastor e161a699b9 musician traductions ca, es 2024-08-06 12:16:53 +02:00
Jorge Pastor 58b377ad86 musician alert on domains-detail 2024-08-01 11:17:53 +02:00
Jorge Pastor 81b314f21a fix migrations, otra vez, Malditas migrations 2024-07-12 11:41:44 +02:00
Jorge Pastor 2e99b8bccd lists traffic Multiply bytes sent by receipts 2024-07-11 19:20:20 +02:00
Jorge Pastor e15d2acd13 mailboxes ratelimit and groups 2024-07-09 17:36:32 +02:00
Jorge Pastor 67c291cb99 mailboxes ratelimit prepare 2024-07-08 18:30:00 +02:00
Jorge Pastor 9c1acfaf8a add backend rspamd ratelimit basic 2024-06-27 18:08:46 +02:00
Jorge Pastor 17e0497ed8 musician new dashborad 2024-06-19 13:41:34 +02:00
Jorge Pastor a00d1eff9f musician dashboard2 version 0.0 2024-06-18 19:38:36 +02:00
Jorge Pastor 35dd1c64c2 musician dashboard2 progresbar exact % and try monitors 2024-06-17 20:54:23 +02:00
Jorge Pastor b72af48cd1 musician dashboard2 show resources 2024-06-13 20:42:49 +02:00
Jorge Pastor cb3e482dfc musician dashboard get all resources 2024-06-10 22:26:55 +02:00
Jorge Pastor abbe58bc96 musician responsive tables to mobile 2024-06-05 20:00:53 +02:00
Jorge Pastor 8cd3605d48 datbases: wait 1.5 second assign user to db after create 2024-06-05 17:47:40 +02:00
Jorge Pastor e1d250c35f musician mailinlist create and delete 2024-06-05 17:14:59 +02:00
Jorge Pastor 281d50acc5 domains change ip rule autoconfig 2024-06-04 18:23:39 +02:00
Jorge Pastor 54c6c59237 musician mailinlist edit 2024-05-29 20:03:25 +02:00
Jorge Pastor 9dc719abcb musician reorder code websites,webapps 2024-05-29 17:55:01 +02:00
Jorge Pastor 0dc4352432 musician website directives clear 2024-05-23 21:46:45 +02:00
Jorge Pastor 475df0a2e5 musician website create directives filter group name and helptext 2024-05-22 20:45:16 +02:00
Jorge Pastor 1ebf30db33 musician website create contents 2024-05-18 14:27:03 +02:00
Jorge Pastor 4f258a9fe0 musician website edit basic 2024-05-16 20:41:46 +02:00
Jorge Pastor f3e3d45d60 musician websitedetail 2024-05-13 20:23:25 +02:00
Jorge Pastor 898dde9330 musician webappdetail show options webapp 2024-05-07 18:52:24 +02:00
Jorge Pastor ad11f64e52 musician menu, add icon user 2024-05-03 09:12:55 +02:00
Jorge Pastor 142c9f97ae musician reload webapp options after save webappoption 2024-05-02 21:21:33 +02:00
Jorge Pastor b911e0e89f musician webapps filter create duplicate options 2024-05-02 19:55:48 +02:00
Jorge Pastor b896dd2262 musician webapps/websites description on listview 2024-04-29 21:40:49 +02:00
Jorge Pastor 6ff6a75eda musician add webapps/websites on menu 2024-04-29 21:01:51 +02:00
Jorge Pastor 4688605bc6 musician webapp edit options 2024-04-29 20:06:20 +02:00
Jorge Pastor fa1a130370 musician webapp edit, add helptext 2024-04-25 22:20:26 +02:00
Jorge Pastor 3c3e15d1c5 musician webapp visualization and firsts edits 2024-04-25 17:21:50 +02:00
Jorge Pastor bb4fc5e267 musician websites visualizacion 2024-04-23 17:14:39 +02:00
Jorge Pastor 60771b7808 musician crypt password when create mailboxuser 2024-04-18 16:58:37 +02:00
Jorge Pastor 5c0c82d50b musician add webappusers and systemusers 2024-04-16 20:49:11 +02:00
Jorge Pastor ee7cf07294 replace . for hash on names databases 2024-04-10 12:07:50 +02:00
Jorge Pastor ba1ceede15 rewrite resource trafic mailman 2024-03-22 19:00:43 +01:00
Jorge Pastor c80295c277 move nextcloud api and quota to file init to correct visualization 2024-03-19 20:08:13 +01:00
Jorge Pastor 1724309769 update mailbox trafic 2024-03-11 18:31:07 +01:00
Jorge Pastor d29ffa900d fix resource aggregations when not found dataset 2024-03-11 13:49:03 +01:00
Jorge Pastor ae88cc4911 firs version trafic mail 2024-03-08 20:37:19 +01:00
Jorge Pastor 3063e78dd9 add webapp limesurvey 2024-03-06 22:52:19 +01:00
Jorge Pastor 39267baaad fix create list with same name and addres_name 2024-03-06 13:11:17 +01:00
Jorge Pastor 15fe9fa8cf add prometheus client on requeriments 2024-03-03 12:17:53 +01:00
Jorge Pastor e1edd4f546 fusion musician with metrics 2024-03-03 12:08:36 +01:00
Jorge Pastor 2b635be17e change 2024-03-03 11:36:54 +01:00
Jorge Pastor 84880401ea hidden disck usage on dashboard website 2024-03-03 11:22:28 +01:00
Jorge Pastor 666126e2de Merge branch 'musician' into develmain 2024-03-03 11:08:58 +01:00
Jorge Pastor c47e5ac8f3 Merge branch 'resources' into develmain 2024-03-03 11:06:11 +01:00
Jorge Pastor 3d798f0cbb allow _ in CNAMEs 2024-02-26 14:04:16 +01:00
Jorge Pastor 16f1d20be2 add CAA record on DNS 2024-02-26 13:53:01 +01:00
Jorge Pastor 6ad5c29035 aumentar proces on resources 2024-02-09 07:13:51 +01:00
Jorge Pastor a717969926 id_launch in monitordata 2024-02-03 22:43:27 +01:00
Santiago L ee9416d8ed Set order_by on DatabaseListView queryset 2024-01-30 13:47:22 +01:00
Santiago L 156ab51f33 Cosmetics: make database list more compact 2024-01-30 13:43:27 +01:00
Santiago L 75d743cab2 Rename databases.html => database_list.html
More djangonist template name pattern
2024-01-30 12:58:53 +01:00
Santiago L 849af04e4b Add pagination nav 2024-01-30 12:55:18 +01:00
Santiago L aea944a732 Cosmetics: make SaaS list more compact 2024-01-30 12:51:28 +01:00
Santiago L 5836d6e6ed Allow search mailboxes by name or domain 2024-01-30 11:31:24 +01:00
Santiago L b21bc1363d Allow filter address by domain name 2024-01-29 18:32:44 +01:00
Santiago L 52ec1660f6 Retrieve database disk resource usage 2024-01-29 17:59:38 +01:00
Santiago L 0695099980 Drop disk & transfer usage as always shows N/A
Show it again when valuable information could be provided
to the user.
2024-01-29 17:29:53 +01:00
Santiago L 0b31049f0b Fix compatibility with Django 2.2.X
url_has_allowed_host_and_scheme has been introduced on 3.1
2024-01-29 17:26:26 +01:00
Santiago L 1bd7023fdf Rename templates to be more djangonist 2024-01-26 14:23:03 +01:00
Santiago L 40cb2b45bb Upgrade to be compatible with django 3.2 2024-01-26 14:22:52 +01:00
Jorge Pastor 01f8ac5481 metrics add url default metrics 2024-01-25 20:44:47 +01:00
Jorge Pastor 7ec7d18544 metrics reduce code 2024-01-25 20:22:04 +01:00
Jorge Pastor 4cfbf57428 metrics reduce code get top 10 resources 2024-01-25 19:34:54 +01:00
Jorge Pastor dc2927bc88 metrics lists and saas 2024-01-24 20:28:12 +01:00
Jorge Pastor 3d8fddfd9e metrics databases 2024-01-24 18:58:54 +01:00
Jorge Pastor 031a290390 metrics account and websites 2024-01-17 18:03:56 +00:00
Jorge Pastor 1716ea36d0 add url metrics 2024-01-14 10:31:23 +01:00
454 changed files with 44631 additions and 6278 deletions

View File

@ -1,13 +1,12 @@
from functools import partial from functools import partial
from django.contrib import admin from django.contrib import admin
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mass_mail from django.core.mail import send_mass_mail
from django.shortcuts import render from django.shortcuts import render
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext, gettext_lazy as _
from django.utils.translation import ngettext
from .. import settings from .. import settings
from .decorators import action_with_confirmation from .decorators import action_with_confirmation
from .forms import SendEmailForm from .forms import SendEmailForm
@ -19,7 +18,7 @@ class SendEmail(object):
template = 'admin/orchestra/generic_confirmation.html' template = 'admin/orchestra/generic_confirmation.html'
default_from = settings.ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL 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
@ -35,10 +34,10 @@ class SendEmail(object):
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
} }
return self.write_email(request) return self.write_email(request)
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
initial={ initial={
'email_from': self.default_from, 'email_from': self.default_from,
'to': ' '.join(self.get_email_addresses()) 'to': ' '.join(self.get_email_addresses())
@ -52,7 +51,7 @@ class SendEmail(object):
'extra_to': form.cleaned_data['extra_to'], 'extra_to': form.cleaned_data['extra_to'],
'subject': form.cleaned_data['subject'], 'subject': form.cleaned_data['subject'],
'message': form.cleaned_data['message'], 'message': form.cleaned_data['message'],
} }
return self.confirm_email(request, **options) return self.confirm_email(request, **options)
self.context.update({ self.context.update({
@ -63,10 +62,10 @@ class SendEmail(object):
}) })
# Display confirmation page # Display confirmation page
return render(request, self.template, self.context) return render(request, self.template, self.context)
def get_email_addresses(self): def get_email_addresses(self):
return self.queryset.values_list('email', flat=True) return self.queryset.values_list('email', flat=True)
def confirm_email(self, request, **options): def confirm_email(self, request, **options):
email_from = options['email_from'] email_from = options['email_from']
extra_to = options['extra_to'] extra_to = options['extra_to']
@ -89,7 +88,7 @@ class SendEmail(object):
) )
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, 'email_from': email_from,
'extra_to': ', '.join(extra_to), 'extra_to': ', '.join(extra_to),
@ -132,19 +131,15 @@ def base_disable(modeladmin, request, queryset, disable=True):
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
@admin.action(
description=_("Disable")
)
@action_with_confirmation() @action_with_confirmation()
def disable(modeladmin, request, queryset): def disable(modeladmin, request, queryset):
return base_disable(modeladmin, request, queryset) return base_disable(modeladmin, request, queryset)
disable.url_name = 'disable' disable.url_name = 'disable'
disable.short_description = _("Disable")
@admin.action(
description=_("Enable")
)
@action_with_confirmation() @action_with_confirmation()
def enable(modeladmin, request, queryset): def enable(modeladmin, request, queryset):
return base_disable(modeladmin, request, queryset, disable=False) return base_disable(modeladmin, request, queryset, disable=False)
enable.url_name = 'enable' enable.url_name = 'enable'
enable.short_description = _("Enable")

View File

@ -1,16 +1,16 @@
from urllib import parse from urllib import parse
from django import forms from django import forms
from django.urls import re_path as url
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.admin.options import IS_POPUP_VAR from django.contrib.admin.options import IS_POPUP_VAR
from django.contrib.admin.utils import unquote from django.contrib.admin.utils import unquote
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect, Http404, HttpResponse
from django.forms.models import BaseInlineFormSet from django.forms.models import BaseInlineFormSet
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import re_path as url
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.html import escape from django.utils.html import escape
@ -19,12 +19,14 @@ from django.views.decorators.debug import sensitive_post_parameters
from orchestra.models.utils import has_db_field from orchestra.models.utils import has_db_field
from ..utils.python import pairwise, random_ascii from ..utils.python import random_ascii, pairwise
from .forms import AdminPasswordChangeForm from .forms import AdminPasswordChangeForm
#, AdminRawPasswordChangeForm #, AdminRawPasswordChangeForm
#from django.contrib.auth.forms import AdminPasswordChangeForm #from django.contrib.auth.forms import AdminPasswordChangeForm
from .utils import action_to_view from .utils import action_to_view
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters()) sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
@ -35,7 +37,7 @@ class ChangeListDefaultFilter(object):
default_changelist_filters = (('my_nodes', 'True'),) default_changelist_filters = (('my_nodes', 'True'),)
""" """
default_changelist_filters = () default_changelist_filters = ()
def changelist_view(self, request, extra_context=None): def changelist_view(self, request, extra_context=None):
# defaults = [] # defaults = []
# for key, value in self.default_changelist_filters: # for key, value in self.default_changelist_filters:
@ -77,7 +79,7 @@ class EnhaceSearchMixin(object):
if 'password' in lookup: if 'password' in lookup:
return False return False
return True return True
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)
@ -107,7 +109,7 @@ class ChangeViewActionsMixin(object):
""" Makes actions visible on the admin change view page. """ """ Makes actions visible on the admin change view page. """
change_view_actions = () change_view_actions = ()
change_form_template = 'orchestra/admin/change_form.html' change_form_template = 'orchestra/admin/change_form.html'
def get_urls(self): def get_urls(self):
"""Returns the additional urls for the change view links""" """Returns the additional urls for the change view links"""
urls = super(ChangeViewActionsMixin, self).get_urls() urls = super(ChangeViewActionsMixin, self).get_urls()
@ -122,7 +124,7 @@ class ChangeViewActionsMixin(object):
) )
) )
return new_urls + urls return new_urls + urls
def get_change_view_actions(self, obj=None): def get_change_view_actions(self, obj=None):
""" allow customization on modelamdin """ """ allow customization on modelamdin """
views = [] views = []
@ -143,7 +145,7 @@ class ChangeViewActionsMixin(object):
view.hidden = getattr(action, 'hidden', False) view.hidden = getattr(action, 'hidden', False)
views.append(view) views.append(view)
return views return views
def change_view(self, request, object_id, **kwargs): def change_view(self, request, object_id, **kwargs):
if kwargs.get('extra_context', None) is None: if kwargs.get('extra_context', None) is None:
kwargs['extra_context'] = {} kwargs['extra_context'] = {}
@ -163,21 +165,21 @@ class ChangeAddFieldsMixin(object):
change_readonly_fields = () change_readonly_fields = ()
change_form = None change_form = None
add_inlines = None add_inlines = None
def get_prepopulated_fields(self, request, obj=None): def get_prepopulated_fields(self, request, obj=None):
if not obj: if not obj:
return super(ChangeAddFieldsMixin, self).get_prepopulated_fields(request, obj) return super(ChangeAddFieldsMixin, self).get_prepopulated_fields(request, obj)
return {} return {}
def get_change_readonly_fields(self, request, obj=None): def get_change_readonly_fields(self, request, obj=None):
return self.change_readonly_fields return self.change_readonly_fields
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) fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj)
if obj: if obj:
return fields + self.get_change_readonly_fields(request, obj) return fields + self.get_change_readonly_fields(request, obj)
return fields return fields
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
if not obj: if not obj:
if self.add_fieldsets: if self.add_fieldsets:
@ -185,7 +187,7 @@ class ChangeAddFieldsMixin(object):
elif self.add_fields: elif self.add_fields:
return [(None, {'fields': self.add_fields})] return [(None, {'fields': self.add_fields})]
return super(ChangeAddFieldsMixin, self).get_fieldsets(request, obj) return super(ChangeAddFieldsMixin, self).get_fieldsets(request, obj)
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 """
if obj: if obj:
@ -196,7 +198,7 @@ class ChangeAddFieldsMixin(object):
for inline in inlines: for inline in inlines:
inline.parent_object = obj inline.parent_object = obj
return inlines return inlines
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
""" Use special form during user creation """ """ Use special form during user creation """
defaults = {} defaults = {}
@ -216,13 +218,13 @@ class ExtendedModelAdmin(ChangeViewActionsMixin,
EnhaceSearchMixin, EnhaceSearchMixin,
admin.ModelAdmin): admin.ModelAdmin):
list_prefetch_related = None list_prefetch_related = None
def get_queryset(self, request): def get_queryset(self, request):
qs = super(ExtendedModelAdmin, self).get_queryset(request) qs = super(ExtendedModelAdmin, self).get_queryset(request)
if self.list_prefetch_related: if self.list_prefetch_related:
qs = qs.prefetch_related(*self.list_prefetch_related) qs = qs.prefetch_related(*self.list_prefetch_related)
return qs return qs
def get_object(self, request, object_id, from_field=None): def get_object(self, request, object_id, from_field=None):
obj = super(ExtendedModelAdmin, self).get_object(request, object_id, from_field) obj = super(ExtendedModelAdmin, self).get_object(request, object_id, from_field)
if obj is None: if obj is None:
@ -235,7 +237,7 @@ class ExtendedModelAdmin(ChangeViewActionsMixin,
class ChangePasswordAdminMixin(object): class ChangePasswordAdminMixin(object):
change_password_form = AdminPasswordChangeForm change_password_form = AdminPasswordChangeForm
change_user_password_template = 'admin/orchestra/change_password.html' change_user_password_template = 'admin/orchestra/change_password.html'
def get_urls(self): def get_urls(self):
opts = self.model._meta opts = self.model._meta
info = opts.app_label, opts.model_name info = opts.app_label, opts.model_name
@ -247,14 +249,14 @@ class ChangePasswordAdminMixin(object):
self.admin_site.admin_view(self.show_hash), self.admin_site.admin_view(self.show_hash),
name='%s_%s_show_hash' % info) name='%s_%s_show_hash' % info)
] + super().get_urls() ] + super().get_urls()
def get_change_password_username(self, obj): def get_change_password_username(self, obj):
return str(obj) return str(obj)
@sensitive_post_parameters_m @sensitive_post_parameters_m
def change_password(self, request, id, form_url=''): def change_password(self, request, id, form_url=''):
if not self.has_change_permission(request): if not self.has_change_permission(request):
raise PermissionDenied() raise PermissionDenied
# TODO use this insetad of self.get_object(), in other places # TODO use this insetad of self.get_object(), in other places
obj = get_object_or_404(self.get_queryset(request), pk=id) obj = get_object_or_404(self.get_queryset(request), pk=id)
raw = request.GET.get('raw', '0') == '1' raw = request.GET.get('raw', '0') == '1'
@ -279,7 +281,7 @@ class ChangePasswordAdminMixin(object):
for rel in account.get_related_passwords(db_field=raw): for rel in account.get_related_passwords(db_field=raw):
if not isinstance(obj, type(rel)): if not isinstance(obj, type(rel)):
related.append(rel) related.append(rel)
if request.method == 'POST': if request.method == 'POST':
form = self.change_password_form(obj, request.POST, related=related, raw=raw) form = self.change_password_form(obj, request.POST, related=related, raw=raw)
if form.is_valid(): if form.is_valid():
@ -291,7 +293,7 @@ class ChangePasswordAdminMixin(object):
return HttpResponseRedirect('..') return HttpResponseRedirect('..')
else: else:
form = self.change_password_form(obj, related=related, raw=raw) form = self.change_password_form(obj, related=related, raw=raw)
fieldsets = [ fieldsets = [
(obj._meta.verbose_name.capitalize(), { (obj._meta.verbose_name.capitalize(), {
'classes': ('wide',), 'classes': ('wide',),
@ -303,7 +305,7 @@ class ChangePasswordAdminMixin(object):
'classes': ('wide',), 'classes': ('wide',),
'fields': ('password_%i' % ix,) if raw else ('password1_%i' % ix, 'password2_%i' % ix) 'fields': ('password_%i' % ix,) if raw else ('password1_%i' % ix, 'password2_%i' % ix)
})) }))
obj_username = self.get_change_password_username(obj) obj_username = self.get_change_password_username(obj)
adminForm = admin.helpers.AdminForm(form, fieldsets, {}) adminForm = admin.helpers.AdminForm(form, fieldsets, {})
context = { context = {
@ -329,9 +331,9 @@ class ChangePasswordAdminMixin(object):
} }
context.update(admin.site.each_context(request)) context.update(admin.site.each_context(request))
return TemplateResponse(request, self.change_user_password_template, context) return TemplateResponse(request, self.change_user_password_template, context)
def show_hash(self, request, id): def show_hash(self, request, id):
if not request.user.is_superuser: if not request.user.is_superuser:
raise PermissionDenied() raise PermissionDenied
obj = get_object_or_404(self.get_queryset(request), pk=id) obj = get_object_or_404(self.get_queryset(request), pk=id)
return HttpResponse(obj.password) return HttpResponse(obj.password)

View File

@ -145,8 +145,6 @@ DATABASES = {
} }
} }
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Password validation # Password validation
# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#auth-password-validators

View File

@ -1,6 +1,6 @@
from django.urls import include, path from django.conf.urls import include, url
urlpatterns = [ urlpatterns = [
path('', include('orchestra.urls')), url(r'', include('orchestra.urls')),
] ]

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.accounts.apps.AccountConfig'

View File

@ -29,7 +29,6 @@ from .models import Account
from .filters import HasTipeServerFilter from .filters import HasTipeServerFilter
@admin.register(Account)
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin): class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
list_display = ('username', 'full_name', 'type', 'is_active') list_display = ('username', 'full_name', 'type', 'is_active')
list_filter = ( list_filter = (
@ -151,6 +150,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
return actions return actions
admin.site.register(Account, AccountAdmin)
class AccountListAdmin(AccountAdmin): class AccountListAdmin(AccountAdmin):
@ -159,10 +159,6 @@ class AccountListAdmin(AccountAdmin):
actions = None actions = None
change_list_template = 'admin/accounts/account/select_account_list.html' change_list_template = 'admin/accounts/account/select_account_list.html'
@admin.display(
description=_("account"),
ordering='username',
)
@mark_safe @mark_safe
def select_account(self, instance): def select_account(self, instance):
# TODO get query string from request.META['QUERY_STRING'] to preserve filters # TODO get query string from request.META['QUERY_STRING'] to preserve filters
@ -172,6 +168,8 @@ class AccountListAdmin(AccountAdmin):
'plus': '<strong style="color:green; font-size:12px">+</strong>', 'plus': '<strong style="color:green; font-size:12px">+</strong>',
} }
return _('<a href="%(url)s">%(plus)s Add to %(name)s</a>') % context return _('<a href="%(url)s">%(plus)s Add to %(name)s</a>') % context
select_account.short_description = _("account")
select_account.admin_order_field = 'username'
def changelist_view(self, request, extra_context=None): def changelist_view(self, request, extra_context=None):
app_label = request.META['PATH_INFO'].split('/')[-5] app_label = request.META['PATH_INFO'].split('/')[-5]
@ -210,10 +208,6 @@ class AccountAdminMixin(object):
account = None account = None
list_select_related = ('account',) list_select_related = ('account',)
@admin.display(
description=_("active"),
ordering='is_active',
)
@mark_safe @mark_safe
def display_active(self, instance): def display_active(self, instance):
if not instance.is_active: if not instance.is_active:
@ -222,14 +216,14 @@ class AccountAdminMixin(object):
msg = _("Account disabled") msg = _("Account disabled")
return '<img style="width:13px" src="%s" alt="False" title="%s">' % (static('admin/img/inline-delete.svg'), msg) return '<img style="width:13px" src="%s" alt="False" title="%s">' % (static('admin/img/inline-delete.svg'), msg)
return '<img src="%s" alt="False">' % static('admin/img/icon-yes.svg') return '<img src="%s" alt="False">' % static('admin/img/icon-yes.svg')
display_active.short_description = _("active")
display_active.admin_order_field = 'is_active'
@admin.display(
description=_("account"),
ordering='account__username',
)
def account_link(self, instance): def account_link(self, instance):
account = instance.account if instance.pk else self.account account = instance.account if instance.pk else self.account
return admin_link()(account) return admin_link()(account)
account_link.short_description = _("account")
account_link.admin_order_field = 'account__username'
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
""" Warns user when object's account is disabled """ """ Warns user when object's account is disabled """

View File

@ -1,39 +1,40 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:08
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations import django.contrib.auth.models
import django.core.validators import django.core.validators
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import django.contrib.auth.models import orchestra.contrib.accounts.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
# Permissions and contenttypes
('auth', '0006_require_contenttypes_0002'),
('systemusers', '0001_initial'), ('systemusers', '0001_initial'),
('auth', '0006_require_contenttypes_0002'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Account', name='Account',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(verbose_name='password', max_length=128)), ('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, verbose_name='last login', null=True)), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('username', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], max_length=32, verbose_name='username')), ('username', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username')),
('short_name', models.CharField(blank=True, verbose_name='short name', max_length=64)), ('short_name', models.CharField(blank=True, max_length=64, verbose_name='short name')),
('full_name', models.CharField(verbose_name='full name', max_length=256)), ('full_name', models.CharField(max_length=256, verbose_name='full name')),
('email', models.EmailField(help_text='Used for password recovery', max_length=254, verbose_name='email address')), ('email', models.EmailField(help_text='Used for password recovery', max_length=254, verbose_name='email address')),
('type', models.CharField(verbose_name='type', choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], max_length=32, default='INDIVIDUAL')), ('type', models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type')),
('language', models.CharField(verbose_name='language', choices=[('EN', 'English')], max_length=2, default='EN')), ('language', models.CharField(choices=[('EN', 'English')], default='EN', max_length=2, verbose_name='language')),
('comments', models.TextField(blank=True, verbose_name='comments', max_length=256)), ('comments', models.TextField(blank=True, max_length=256, verbose_name='comments')),
('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', default=False, verbose_name='superuser status')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('is_active', models.BooleanField(help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', default=True, verbose_name='active')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('main_systemuser', models.ForeignKey(to='systemusers.SystemUser', editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accounts_main')), ('main_systemuser', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accounts_main', to='systemusers.SystemUser')),
], ],
options={ options={
'abstract': False, 'abstract': False,
@ -42,4 +43,40 @@ class Migration(migrations.Migration):
('objects', django.contrib.auth.models.UserManager()), ('objects', django.contrib.auth.models.UserManager()),
], ],
), ),
migrations.AlterModelManagers(
name='account',
managers=[
('objects', orchestra.contrib.accounts.models.AccountManager()),
],
),
migrations.AlterField(
model_name='account',
name='language',
field=models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language'),
),
migrations.AlterField(
model_name='account',
name='type',
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='account',
name='username',
field=models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username'),
),
migrations.AlterField(
model_name='account',
name='language',
field=models.CharField(choices=[('EN', 'English')], default='EN', max_length=2, verbose_name='language'),
),
migrations.AlterField(
model_name='account',
name='type',
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='account',
name='main_systemuser',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='accounts_main', to='systemusers.SystemUser'),
),
] ]

View File

@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:08
from __future__ import unicode_literals
import django.contrib.auth.models
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import orchestra.contrib.accounts.models
class Migration(migrations.Migration):
replaces = [('accounts', '0001_initial'), ('accounts', '0002_auto_20170528_2005'), ('accounts', '0003_auto_20210330_1049'), ('accounts', '0004_auto_20210422_1108')]
initial = True
dependencies = [
('systemusers', '0001_initial'),
('auth', '0006_require_contenttypes_0002'),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('username', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username')),
('short_name', models.CharField(blank=True, max_length=64, verbose_name='short name')),
('full_name', models.CharField(max_length=256, verbose_name='full name')),
('email', models.EmailField(help_text='Used for password recovery', max_length=254, verbose_name='email address')),
('type', models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type')),
('language', models.CharField(choices=[('EN', 'English')], default='EN', max_length=2, verbose_name='language')),
('comments', models.TextField(blank=True, max_length=256, verbose_name='comments')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('main_systemuser', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accounts_main', to='systemusers.SystemUser')),
],
options={
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.AlterModelManagers(
name='account',
managers=[
('objects', orchestra.contrib.accounts.models.AccountManager()),
],
),
migrations.AlterField(
model_name='account',
name='language',
field=models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language'),
),
migrations.AlterField(
model_name='account',
name='type',
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='account',
name='username',
field=models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username'),
),
migrations.AlterField(
model_name='account',
name='language',
field=models.CharField(choices=[('EN', 'English')], default='EN', max_length=2, verbose_name='language'),
),
migrations.AlterField(
model_name='account',
name='type',
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='account',
name='main_systemuser',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='accounts_main', to='systemusers.SystemUser'),
),
]

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-05-28 18:05
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import orchestra.contrib.accounts.models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.AlterModelManagers(
name='account',
managers=[
('objects', orchestra.contrib.accounts.models.AccountManager()),
],
),
migrations.AlterField(
model_name='account',
name='language',
field=models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language'),
),
migrations.AlterField(
model_name='account',
name='type',
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='account',
name='username',
field=models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username'),
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # Generated by Django 2.2.24 on 2024-07-11 12:25
# Generated by Django 1.10.5 on 2021-03-30 10:49
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
@ -8,18 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('accounts', '0002_auto_20170528_2005'), ('accounts', '0001_initial'),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='account', model_name='account',
name='language', name='language',
field=models.CharField(choices=[('EN', 'English')], default='EN', max_length=2, verbose_name='language'), field=models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='account', model_name='account',
name='type', name='type',
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'), field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
), ),
] ]

View File

@ -0,0 +1,42 @@
BBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBB BBBB BBBBBBBBBB BBBBBB BBBBBBBBBBBB
BBBBB BBBBBBBBBBB
XXXX XXXXXXXXXXXXXXXXXXXX
XX XXXXXXBBB BBBBBBBBBBBBBXX gettext(u'Home') XXXX
BB BBBBBBBBBBBB
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFFFFFFFFFFFXXXX
BBBB
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX BB BBBBBBBBBBBBBBBBBBBBBXX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFXXXXBBBBFFFFFFFFBBBBB
BBBBB
BB BBBBBBBBBBB
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXX gettext(u'Select %(name)s account') SSSSSS SSSSSSSS SSSSSSSXXXX
BBBBB
XXXXXXXX BB BBB gettext(u'Add') BBBBFFFFFFFFFFFFFFFFFFBBBBB
XXXXXX
BBBBBBBB
BBBBB BBBBBBBBBBBBBBBBBB
BB BBBBBBBB
XXXXXXXXXXX XXXXXXXXXXXX XXXXXXXXXXXXXXXXXX X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXX XXXX X X XXXX
XXXXXXX XXXXXXXX XXXXXXXXX gettext(u'Services') XXXXXXXXX
BBB BBBBBBB BB BBBBBBBB
XXXXXXX XXXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXXXXXXXFFFFFFFFXXXXXXXXX
BBBBBB
XXXXXXXXXXXXXX
BBBBB
BB BBBBBBBB
XXXXXXXXXXX XXXXXXXXXXXX XXXXXXXXXXXXXXXXXX X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXX XXXX XXX XXX XXXXXX
XXXXXXX XXXXXXXX XXXXXXXXX gettext(u'Accounts') XXXXXXXXX
BBB BBBBBBB BB BBBBBBBB
XXXXXXX XXXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXXXXXXXFFFFFFFFXXXXXXXXX
BBBBBB
XXXXXXXXXXXXXX
BBBBB
BBBBBBBB

View File

@ -0,0 +1,49 @@
BBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB
BBBB BBBB BBBBBBBBBB BBBBBBBBBB
BBBBB BBBBBBBBBBB
XXXX XXXXXXXXXXXXXXXXXXXX
XX XXXXXXBBB BBBBBBBBBBBBBXX gettext(u'Home') XXXX
BB BBBBBBB
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFFFFFFFFFFFXXXX
BBBB
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
BBBBB
XXXXXXXX FFFFFFFF
XXXXXX
BBBBBBBB
BBBBB BBBBBBBBBBBBBBBBBB
XXXX
BBB BBBBBBBBBBBBBBBBBBBBBBBBBBB BB BBBBBBB
XX XXXXXXBBBBBBBBBBBBBBBBBBBBB BBBBBBB BBBBBBBB BBBBBBBBX XXXXXXXXXXXXXXXX
BB BBBBBBBBBBBB
gettext(u'Add %(name)s') SSS SSSSSSSS
BBBB
gettext(u'Add %(account)s %(name)s') SSS SSSSSSSSSSS SSSSSSSS
BBBBB
XXXX
XXXXX
BBBBBBBB
BBBBB BBBBBBB
BB BBBBBBBBBBBBBB
XXXX XXXXXXXXXXXXXXXXXXXXXXX
XXXX gettext(u'Filter') XXXXX
BB BBBBBBB
XXXX gettext(u'By account') XXXXX
XXXX
XXX BB BBB BBBBBBBBBBBBXXXXXXXXXXXXXXXXBBBBBXXX XXXXXXXXXXXXXXXXXFFFFFFFFFFFFFFFFFFXXXXXXXXX
XXX BB BBBBBBBBBBBBXXXXXXXXXXXXXXXXBBBBBXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXX
BBBBB
BBB BBBB BB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BB BBBBBBBBBB
XXXXXX
BBBBB
BBBBBBBB

View File

@ -0,0 +1,39 @@
BBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBB BBBB BBBB BBBBBBBBBB
BBBBB BBBBBBB
BB BBBBBBBBBBBBB
XXX gettext(u"Deleting the selected %(objects_name)s would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:") SSSSSSSS SSS SSSSSSSS SSSSSSSSSSSSSSSS SSSSS SSSSSS SS SSSSSSSS SSSSSSS SSSSSSSS SSS SSSS SSSSSSS SSSSSSS SSSS SSSSSSSSSS SS SSSSSS SSS SSSSSSSSS SSSSS SS SSSSSSSSXXXX
XXXX
BBB BBB BB BBBBBBBBBBBBB
XXXXXXXXX
BBBBBB
XXXXX
BBBB BBBBBBBBB
XXX gettext(u'Deleting the selected %(objects_name)s would require deleting the following protected related objects:') SSSSSSSS SSS SSSSSSSS SSSSSSSSSSSSSSSS SSSSS SSSSSSS SSSSSSSS SSS SSSSSSSSS SSSSSSSSS SSSSSSS SSSSSSSSXXXX
XXXX
BBB BBB BB BBBBBBBBB
XXXXXXXXX
BBBBBB
XXXXX
BBBB
XXX gettext(u'Are you sure you want to delete the selected %(objects_name)s? All of the following objects and their related items will be deleted:') SSS SSS SSSS SSS SSSS SS SSSSSS SSS SSSSSSSS SSSSSSSSSSSSSSSSS SSS SS SSS SSSSSSSSS SSSSSSS SSS SSSSS SSSSSSS SSSSS SSSS SS SSSSSSSSXXXX
BBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
XXXX gettext(u'Objects') XXXXX
BBB BBBBBBBBBBBBBBBB BB BBBBBBBBBBBBBBBBB
XXXXFFFFFFFFFFFFFFXXXXX
BBBBBB
XXXXX XXXXXXXXX XXXXXXXXXXXXXXBBBBBBBBBB
XXXXX
BBB BBB BB BBBBBBBB
XXXXXX XXXXXXXXXXXXX XXXXXXX XXXXXXXFFFFFFFFFFX XX
BBBBBB
XXXXXX XXXXXXXXXXXXX XXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX
XXXXXX XXXXXXXXXXXXX XXXXXXXXXXX XXXXXXXXXXX XX
XXXXXX XXXXXXXXXXXXX XXXXXXX gettext(u"Yes, I'm sure") X XX
XX XXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXX XXXXXXXXXXXXX gettext(u'No, take me back') XXXX
XXXXXX
XXXXXXX
BBBBB
BBBBBBBB

View File

@ -0,0 +1,35 @@
BBBBBBB BBBBBBBBBBBBBBBBBBBBBB
BBBB BBBB BBBB BBBBBBBBBB
BBBBB BBBBBBBBB XXXX XXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXBBBBBBBB
BBBBB BBBBBBBBBBB
XXXX XXXXXXXXXXXXXXXXXXXX
XX XXXXXXBBB BBBBBBBBBBBBBXX gettext(u'Home') XXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFXXXX
XXXXXXXX BB BBBBBBB gettext(u'Disable %(objects_name)s') SSSSSSS SSSSSSSSSSSSSSSSBBBB gettext(u'Enable %(objects_name)s') SSSSSS SSSSSSSSSSSSSSSSBBBBB
XXXXXX
BBBBBBBB
BBBBB BBBBBBB
BB BBBBBBBXXX gettext(u'Are you sure you want to disable selected %(objects_name)s?') SSS SSS SSSS SSS SSSS SS SSSSSSS SSSSSSSS SSSSSSSSSSSSSSSSSXXXX
BBBBXXX gettext(u'Are you sure you want to enable selected %(objects_name)s?') SSS SSS SSSS SSS SSSS SS SSSSSS SSSSSSSS SSSSSSSSSSSSSSSSSXXXX
BBBBB
XXXX gettext(u'Objects') XXXXX
BBB BBBBBBBBBBBBBBBB BB BBBBBBBBBBBBBBBBB
XXXXFFFFFFFFFFFFFFXXXXX
BBBBBB
XXXXX XXXXXXXXX XXXXXXXXXXXXXXBBBBBBBBBB
XXXXX
BBB BBB BB BBBBBBBB
XXXXXX XXXXXXXXXXXXX XXXXXXX XXXXXXXFFFFFFFFFFX XX
BBBBBB
XXXXXX XXXXXXXXXXXXX XXXXXXXXXXXXX XXXXXXXX XX
XXXXXX XXXXXXXXXXXXX XXXXXXXXXXX XXXXXXXXXXX XX
XXXXXX XXXXXXXXXXXXX XXXXXXX gettext(u"Yes, I'm sure") X XX
XX XXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXX XXXXXXXXXXXXX gettext(u'No, take me back') XXXX
XXXXXX
XXXXXXX
BBBBBBBB

View File

@ -0,0 +1,13 @@
BBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB
BBBB BBBB BBBBBBBBBB
BBBBB BBBBBBBBBBB
XXXX XXXXXXXXXXXXXXXXXXXX
XX XXXXXXBBB BBBBBBBBBBBBBXX gettext(u'Home') XXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFXXXX
XXXXXXXX gettext(u'Select %(name)s account') SSSSSS SSSSSSSS SSSSSSS
XXXXXX
BBBBBBBB

View File

@ -0,0 +1,84 @@
BBBB BBBB BBBBBBBBBB BBBBB
XXXXXX
XXXXXX
XXXXXXXBBBBB BBBBBXXXXXXX XXXXXXX XXXXXXBBBBBBBBXXXXXXXX
XXXXX XXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXX
BBBBB BBBBBBBBBBBB
XXXXXX XXXXXXXXXXXXXXXX
XXXX X
XXXXXXXXXX XXXXXX
XXXXXXX XX XXXX XXXXXXXXXXX
XXXXXX XXXX XXXXXXXXXXX
XXXXXXXXXXXX XXXXXX XXXXXXXXXXX XXXXXX XXXXXXX XXXXXX XXXXXXXXXXX
XXXXXXXXXX XXXXX
XXXXXX XXXXX
X
XXXXX X
XXXXXX XXXXXX
XXXXXX XXXXXXXX XXXX XXXXX
X
XXXXXXXXXXXXXXXX X
XXXXXXX XXX XXX XXXX XXXXX
X
XXXXXXXXXXX X
XXXXXXXXXXXXXXXX XXXXX
XXXXXXXXXXXX XXXXX
XXXXXX XXXXX
X
XXXXXXXXX X
XXXXXXXX XXXX
XXXXXXX XXX XXX XXXX XXXXX
X
XXXXXXXX X
XXXXXXXXXXX XXXXX
X
XX X
XXXXXXXXXXX XXXXX
X
X X
XXXXXXXXXXXXXXXX XXXXX
XXXXXX XXXXXXX XXXX XXXXX
X
XXXXXXXX
XXXXXXX
XXXXXX
XXXX XXXXXXXXXX gettext(u'Service report generated on') FFFFXXXXXX
BBB BBBBBBBB BBBBB BB BBBBBBBB
XXXX X XX XXXXXXFFFFFFFFFXXXXXXXXXXX
XXXX
XXXX XXXXXXXXXXXXXXXXXXXXXXXX
gettext(u'account registered on') FFFFXXXX
XXX XXXXXXXXXXXXXXXXX
XXX XXXXXXXXXXXXXXXXXXX gettext(u'Resources') XXXXX
BB BBBBBBBBBBBBBBBBB
XXXX
BBB BBBBBBBB BB BBBBBBBBBBBBBBBBB
XXXXXX XXXXXXFFFFFFFFFXX BB BBBBBBBBBBBBB BB BBBBXXXXX XXXXXXX gettext(u'Used') XXXXXXXXXBBBBBBB BBBBBBBBBBBBBBBBBB BB BBBBBB BBBBBBBBBBBBB BB BBBB X BBBBBXXXXX XXXXXXX gettext(u'Allocated') XXXXXXXXXBBBBBXXXX XXXXX
BBBBBB
XXXXX
BBBBB
BBB BBBBB BBBBBBB BB BBBBB
XXX XXXXXXXXXXXXXXXXXXXXX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXXXXXXXXXXFFFFFFFFXXXXXXXXX
XXXX
BBB BBB BB BBBBBBB
XXX XXXXXXXXXXXXXXXXXX XXXXXXFFFFFFFFFXXXXXX
BB BBB BBBBBBBBBBBB X gettext(u'disabled') XBBBBB
FFFFFFFF
BB BBBBBBBBBBBBB
XXXX
BBB BBBBBBBB BB BBBBBBBBBBBBB
XXXXXX XXXXXXFFFFFFFFFXX BB BBBBBBBBBBBBB BB BBBBXXXXX XXXXXXX gettext(u'Used') XXXXXXXXXBBBBBBB BBBBBBBBBBBBBBBBBB BB BBBBBB BBBBBBBBBBBBB BB BBBB X BBBBBXXXXX XXXXXXX gettext(u'Allocated') XXXXXXXXXBBBBBXXXX XXXXX
BBBBBB
XXXXX
BBBBB
XXXXX
BBBBBB
XXXXX
BBBBBB
XXXXX
XXXXXX
BBBBBB
XXXXXXX
XXXXXXX

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.bills.apps.BillsConfig'

View File

@ -68,9 +68,6 @@ class BillLineInline(admin.TabularInline):
order_link = admin_link('order', display='pk') order_link = admin_link('order', display='pk')
@admin.display(
description=_("Total")
)
@mark_safe @mark_safe
def display_total(self, line): def display_total(self, line):
if line.pk: if line.pk:
@ -82,6 +79,7 @@ class BillLineInline(admin.TabularInline):
img = static('admin/img/icon-alert.svg') img = static('admin/img/icon-alert.svg')
return '<a href="%s" title="%s">%s <img src="%s"></img></a>' % (url, content, total, img) return '<a href="%s" title="%s">%s <img src="%s"></img></a>' % (url, content, total, img)
return '<a href="%s">%s</a>' % (url, total) return '<a href="%s">%s</a>' % (url, total)
display_total.short_description = _("Total")
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """
@ -107,38 +105,31 @@ class ClosedBillLineInline(BillLineInline):
readonly_fields = fields readonly_fields = fields
can_delete = False can_delete = False
@admin.display(
description=_("Description")
)
@mark_safe @mark_safe
def display_description(self, line): def display_description(self, line):
descriptions = [line.description] descriptions = [line.description]
for subline in line.sublines.all(): for subline in line.sublines.all():
descriptions.append('&nbsp;' * 4 + subline.description) descriptions.append('&nbsp;' * 4 + subline.description)
return '<br>'.join(descriptions) return '<br>'.join(descriptions)
display_description.short_description = _("Description")
@admin.display(
description=_("Subtotal")
)
@mark_safe @mark_safe
def display_subtotal(self, line): def display_subtotal(self, line):
subtotals = ['&nbsp;' + str(line.subtotal)] subtotals = ['&nbsp;' + str(line.subtotal)]
for subline in line.sublines.all(): for subline in line.sublines.all():
subtotals.append(str(subline.total)) subtotals.append(str(subline.total))
return '<br>'.join(subtotals) return '<br>'.join(subtotals)
display_subtotal.short_description = _("Subtotal")
@admin.display(
description=_("Total")
)
def display_total(self, line): def display_total(self, line):
if line.pk: if line.pk:
return line.compute_total() return line.compute_total()
display_total.short_description = _("Total")
def has_add_permission(self, request, obj): def has_add_permission(self, request, obj):
return False return False
@admin.register(BillLine)
class BillLineAdmin(admin.ModelAdmin): class BillLineAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'description', 'bill_link', 'display_is_open', 'account_link', 'rate', 'quantity', 'description', 'bill_link', 'display_is_open', 'account_link', 'rate', 'quantity',
@ -173,27 +164,21 @@ class BillLineAdmin(admin.ModelAdmin):
order_link = admin_link('order') order_link = admin_link('order')
amended_line_link = admin_link('amended_line') amended_line_link = admin_link('amended_line')
@admin.display(
description=_("Is open"),
boolean=True,
)
def display_is_open(self, instance): def display_is_open(self, instance):
return instance.bill.is_open return instance.bill.is_open
display_is_open.short_description = _("Is open")
display_is_open.boolean = True
@admin.display(
description=_("Sublines"),
ordering='subline_total',
)
def display_sublinetotal(self, instance): def display_sublinetotal(self, instance):
total = instance.subline_total total = instance.subline_total
return total if total is not None else '---' return total if total is not None else '---'
display_sublinetotal.short_description = _("Sublines")
display_sublinetotal.admin_order_field = 'subline_total'
@admin.display(
description=_("Total"),
ordering='computed_total',
)
def display_total(self, instance): def display_total(self, instance):
return round(instance.computed_total or 0, 2) return round(instance.computed_total or 0, 2)
display_total.short_description = _("Total")
display_total.admin_order_field = 'computed_total'
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj) fields = super().get_readonly_fields(request, obj)
@ -257,10 +242,6 @@ class BillLineManagerAdmin(BillLineAdmin):
class BillAdminMixin(AccountAdminMixin): class BillAdminMixin(AccountAdminMixin):
@admin.display(
description=_("total"),
ordering='approx_total',
)
@mark_safe @mark_safe
def display_total_with_subtotals(self, bill): def display_total_with_subtotals(self, bill):
if bill.pk: if bill.pk:
@ -271,10 +252,9 @@ class BillAdminMixin(AccountAdminMixin):
subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency)) subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency))
subtotals = '\n'.join(subtotals) subtotals = '\n'.join(subtotals)
return '<span title="%s">%s &%s;</span>' % (subtotals, bill.compute_total(), currency) return '<span title="%s">%s &%s;</span>' % (subtotals, bill.compute_total(), currency)
display_total_with_subtotals.short_description = _("total")
display_total_with_subtotals.admin_order_field = 'approx_total'
@admin.display(
description=_("Payment")
)
@mark_safe @mark_safe
def display_payment_state(self, bill): def display_payment_state(self, bill):
if bill.pk: if bill.pk:
@ -297,6 +277,7 @@ class BillAdminMixin(AccountAdminMixin):
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey') color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format( return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format(
url=url, color=color, name=state, title=title) url=url, color=color, name=state, title=title)
display_payment_state.short_description = _("Payment")
def get_queryset(self, request): def get_queryset(self, request):
qs = super().get_queryset(request) qs = super().get_queryset(request)
@ -330,7 +311,6 @@ class AmendInline(BillAdminMixin, admin.TabularInline):
return False return False
@admin.register(AbonoInvoice, AmendmentFee, AmendmentInvoice, Bill, Fee, Invoice, ProForma)
class BillAdmin(BillAdminMixin, ExtendedModelAdmin): class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
list_display = ( list_display = (
'number', 'type_link', 'account_link', 'closed_on_display', 'updated_on_display', 'number', 'type_link', 'account_link', 'closed_on_display', 'updated_on_display',
@ -389,29 +369,23 @@ class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
# amend_links.short_description = _("Amends") # amend_links.short_description = _("Amends")
# amend_links.allow_tags = True # amend_links.allow_tags = True
@admin.display(
description=_("lines"),
ordering='lines__count',
)
def num_lines(self, bill): def num_lines(self, bill):
return bill.lines__count return bill.lines__count
num_lines.admin_order_field = 'lines__count'
num_lines.short_description = _("lines")
@admin.display(
description=_("total"),
ordering='approx_total',
)
def display_total(self, bill): def display_total(self, bill):
currency = settings.BILLS_CURRENCY.lower() currency = settings.BILLS_CURRENCY.lower()
return format_html('{} &{};', bill.compute_total(), currency) return format_html('{} &{};', bill.compute_total(), currency)
display_total.short_description = _("total")
display_total.admin_order_field = 'approx_total'
@admin.display(
description=_("type"),
ordering='type',
)
def type_link(self, bill): def type_link(self, bill):
bill_type = bill.type.lower() bill_type = bill.type.lower()
url = reverse('admin:bills_%s_changelist' % bill_type) url = reverse('admin:bills_%s_changelist' % bill_type)
return format_html('<a href="{}">{}</a>', url, bill.get_type_display()) return format_html('<a href="{}">{}</a>', url, bill.get_type_display())
type_link.short_description = _("type")
type_link.admin_order_field = 'type'
def get_urls(self): def get_urls(self):
""" Hook bill lines management URLs on bill admin """ """ Hook bill lines management URLs on bill admin """
@ -482,6 +456,14 @@ class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
return super().change_view(request, object_id, **kwargs) return super().change_view(request, object_id, **kwargs)
admin.site.register(Bill, BillAdmin)
admin.site.register(Invoice, BillAdmin)
admin.site.register(AmendmentInvoice, BillAdmin)
admin.site.register(AbonoInvoice, BillAdmin)
admin.site.register(Fee, BillAdmin)
admin.site.register(AmendmentFee, BillAdmin)
admin.site.register(ProForma, BillAdmin)
admin.site.register(BillLine, BillLineAdmin)
class BillContactInline(admin.StackedInline): class BillContactInline(admin.StackedInline):
@ -499,12 +481,10 @@ class BillContactInline(admin.StackedInline):
return super().formfield_for_dbfield(db_field, **kwargs) return super().formfield_for_dbfield(db_field, **kwargs)
@admin.display(
boolean=True,
ordering='billcontact',
)
def has_bill_contact(account): def has_bill_contact(account):
return hasattr(account, 'billcontact') return hasattr(account, 'billcontact')
has_bill_contact.boolean = True
has_bill_contact.admin_order_field = 'billcontact'
insertattr(AccountAdmin, 'inlines', BillContactInline) insertattr(AccountAdmin, 'inlines', BillContactInline)

View File

@ -18,7 +18,7 @@ class BillViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
@action(detail=True, methods=['get']) @action(detail=True, methods=['get'])
def document(self, request, pk): def document(self, request, pk):
bill = self.get_object() bill = self.get_object()
content_type = request.headers.get('accept') content_type = request.META.get('HTTP_ACCEPT')
if content_type == 'application/pdf': if content_type == 'application/pdf':
pdf = html_to_pdf(bill.html or bill.render()) pdf = html_to_pdf(bill.html or bill.render())
return HttpResponse(pdf, content_type='application/pdf') return HttpResponse(pdf, content_type='application/pdf')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.contacts.apps.ContactsConfig'

View File

@ -13,7 +13,6 @@ from .filters import EmailUsageListFilter
from .models import Contact from .models import Contact
@admin.register(Contact)
class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin): class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = ( list_display = (
'dispaly_name', 'email', 'phone', 'phone2', 'country', 'account_link' 'dispaly_name', 'email', 'phone', 'phone2', 'country', 'account_link'
@ -63,12 +62,10 @@ class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin):
) )
actions = (SendEmail(), list_accounts) actions = (SendEmail(), list_accounts)
@admin.display(
description=_("Name"),
ordering='short_name',
)
def dispaly_name(self, contact): def dispaly_name(self, contact):
return str(contact) return str(contact)
dispaly_name.short_description = _("Name")
dispaly_name.admin_order_field = 'short_name'
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """
@ -79,6 +76,7 @@ class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin):
return super(ContactAdmin, self).formfield_for_dbfield(db_field, **kwargs) return super(ContactAdmin, self).formfield_for_dbfield(db_field, **kwargs)
admin.site.register(Contact, ContactAdmin)
class ContactInline(admin.StackedInline): class ContactInline(admin.StackedInline):

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.databases.apps.DatabasesConfig'

View File

@ -14,14 +14,11 @@ from .filters import HasUserListFilter, HasDatabaseListFilter
from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm, DatabaseForm from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm, DatabaseForm
from .models import Database, DatabaseUser from .models import Database, DatabaseUser
@admin.action(
description="Re-save selected objects"
)
def save_selected(modeladmin, request, queryset): def save_selected(modeladmin, request, queryset):
for selected in queryset: for selected in queryset:
selected.save() selected.save()
save_selected.short_description = "Re-save selected objects"
@admin.register(Database)
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ('name', 'type', 'target_server', 'display_users', 'account_link') list_display = ('name', 'type', 'target_server', 'display_users', 'account_link')
list_filter = ('type', HasUserListFilter) list_filter = ('type', HasUserListFilter)
@ -54,10 +51,6 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
filter_horizontal = ['users'] filter_horizontal = ['users']
actions = (list_accounts, save_selected) actions = (list_accounts, save_selected)
@admin.display(
description=_("Users"),
ordering='users__username',
)
@mark_safe @mark_safe
def display_users(self, db): def display_users(self, db):
links = [] links = []
@ -65,6 +58,8 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
link = format_html('<a href="{}">{}</a>', change_url(user), user.username) link = format_html('<a href="{}">{}</a>', change_url(user), user.username)
links.append(link) links.append(link)
return '<br>'.join(links) return '<br>'.join(links)
display_users.short_description = _("Users")
display_users.admin_order_field = 'users__username'
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super(DatabaseAdmin, self).save_model(request, obj, form, change) super(DatabaseAdmin, self).save_model(request, obj, form, change)
@ -82,7 +77,6 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
obj.users.add(user) obj.users.add(user)
@admin.register(DatabaseUser)
class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin): class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin):
list_display = ('username', 'target_server', 'type', 'display_databases', 'account_link') list_display = ('username', 'target_server', 'type', 'display_databases', 'account_link')
list_filter = ('type', HasDatabaseListFilter) list_filter = ('type', HasDatabaseListFilter)
@ -107,10 +101,6 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
list_prefetch_related = ('databases',) list_prefetch_related = ('databases',)
actions = (list_accounts, save_selected) actions = (list_accounts, save_selected)
@admin.display(
description=_("Databases"),
ordering='databases__name',
)
@mark_safe @mark_safe
def display_databases(self, user): def display_databases(self, user):
links = [] links = []
@ -118,6 +108,8 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
link = format_html('<a href="{}">{}</a>', change_url(db), db.name) link = format_html('<a href="{}">{}</a>', change_url(db), db.name)
links.append(link) links.append(link)
return '<br>'.join(links) return '<br>'.join(links)
display_databases.short_description = _("Databases")
display_databases.admin_order_field = 'databases__name'
def get_urls(self): def get_urls(self):
useradmin = UserAdmin(DatabaseUser, self.admin_site) useradmin = UserAdmin(DatabaseUser, self.admin_site)
@ -133,3 +125,5 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
super(DatabaseUserAdmin, self).save_model(request, obj, form, change) super(DatabaseUserAdmin, self).save_model(request, obj, form, change)
admin.site.register(Database, DatabaseAdmin)
admin.site.register(DatabaseUser, DatabaseUserAdmin)

View File

@ -29,6 +29,9 @@ class MySQLController(ServiceController):
# Create database and re-set permissions # Create database and re-set permissions
mysql -e 'CREATE DATABASE `%(database)s`;' || true mysql -e 'CREATE DATABASE `%(database)s`;' || true
mysql mysql -e 'DELETE FROM db WHERE db = "%(database)s";'\ mysql mysql -e 'DELETE FROM db WHERE db = "%(database)s";'\
# wait to create user
sleep 1.5
""") % context """) % context
) )
for user in database.users.all(): for user in database.users.all():
@ -178,7 +181,7 @@ class MysqlDisk(ServiceMonitor):
def get_context(self, db): def get_context(self, db):
context = { context = {
'db_name': db.name, 'db_name': db.name,
'db_dirname': db.name.replace('-', '@002d'), 'db_dirname': db.name.replace('-', '@002d').replace('.', '@002e'),
'db_id': db.pk, 'db_id': db.pk,
'db_type': db.type, 'db_type': db.type,
} }

View File

@ -1,4 +1,6 @@
# Generated by Django 2.2.28 on 2023-06-28 17:06 # -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:25
from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -8,39 +10,69 @@ import orchestra.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Database',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
('type', models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databases', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
],
),
migrations.CreateModel( migrations.CreateModel(
name='DatabaseUser', name='DatabaseUser',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(max_length=16, validators=[orchestra.core.validators.validate_name], verbose_name='username')), ('username', models.CharField(max_length=16, validators=[orchestra.core.validators.validate_name], verbose_name='username')),
('password', models.CharField(max_length=256, verbose_name='password')), ('password', models.CharField(max_length=256, verbose_name='password')),
('type', models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type')), ('type', models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databaseusers', to=settings.AUTH_USER_MODEL, verbose_name='Account')), ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databaseusers', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
], ],
options={ options={
'verbose_name_plural': 'DB users', 'verbose_name_plural': 'DB users',
'unique_together': {('username', 'type')},
}, },
), ),
migrations.CreateModel( migrations.AddField(
name='Database', model_name='database',
fields=[ name='users',
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), field=models.ManyToManyField(blank=True, related_name='databases', to='databases.DatabaseUser', verbose_name='users'),
('name', models.CharField(max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='name')), ),
('type', models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type')), migrations.AlterUniqueTogether(
('comments', models.TextField(blank=True, default='')), name='databaseuser',
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databases', to=settings.AUTH_USER_MODEL, verbose_name='Account')), unique_together=set([('username', 'type')]),
('users', models.ManyToManyField(blank=True, related_name='databases', to='databases.DatabaseUser', verbose_name='users')), ),
], migrations.AlterUniqueTogether(
options={ name='database',
'unique_together': {('name', 'type')}, unique_together=set([('name', 'type')]),
}, ),
migrations.AlterField(
model_name='database',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='databaseuser',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
),
migrations.AddField(
model_name='database',
name='comments',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='database',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='databaseuser',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
), ),
] ]

View File

@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:25
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import orchestra.core.validators
class Migration(migrations.Migration):
replaces = [('databases', '0001_initial'), ('databases', '0002_auto_20170528_2005'), ('databases', '0003_database_comments'), ('databases', '0004_auto_20210330_1049')]
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Database',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
('type', models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databases', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
],
),
migrations.CreateModel(
name='DatabaseUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(max_length=16, validators=[orchestra.core.validators.validate_name], verbose_name='username')),
('password', models.CharField(max_length=256, verbose_name='password')),
('type', models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databaseusers', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
],
options={
'verbose_name_plural': 'DB users',
},
),
migrations.AddField(
model_name='database',
name='users',
field=models.ManyToManyField(blank=True, related_name='databases', to='databases.DatabaseUser', verbose_name='users'),
),
migrations.AlterUniqueTogether(
name='databaseuser',
unique_together=set([('username', 'type')]),
),
migrations.AlterUniqueTogether(
name='database',
unique_together=set([('name', 'type')]),
),
migrations.AlterField(
model_name='database',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='databaseuser',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
),
migrations.AddField(
model_name='database',
name='comments',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='database',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='databaseuser',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
),
]

View File

@ -7,8 +7,8 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('orchestration', '__first__'), ('orchestration', '__first__'),
('databases', '0002_databaseuser_target_server'), ('databases', '0002_databaseuser_target_server'),
] ]
operations = [ operations = [

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2020-02-04 11:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('databases', '0002_auto_20170528_2005'),
]
operations = [
migrations.AddField(
model_name='database',
name='comments',
field=models.TextField(default=''),
),
]

View File

@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-03-30 10:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('databases', '0003_database_comments'),
]
operations = [
migrations.AlterField(
model_name='database',
name='comments',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='database',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
),
migrations.AlterField(
model_name='databaseuser',
name='type',
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # Generated by Django 2.2.24 on 2024-07-11 12:25
# Generated by Django 1.10.5 on 2017-05-28 18:05
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
@ -8,7 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('databases', '0001_initial'), ('databases', '0006_auto_20230705_1237'),
] ]
operations = [ operations = [

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.domains.apps.DomainsConfig'

View File

@ -39,11 +39,9 @@ class DomainInline(admin.TabularInline):
domain_link.short_description = _("Name") domain_link.short_description = _("Name")
account_link = admin_link('account') account_link = admin_link('account')
@admin.display(
description=_("Declared records")
)
def display_records(self, domain): def display_records(self, domain):
return ', '.join([record.type for record in domain.records.all()]) return ', '.join([record.type for record in domain.records.all()])
display_records.short_description = _("Declared records")
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):
return False return False
@ -54,7 +52,6 @@ class DomainInline(admin.TabularInline):
return qs.select_related('account').prefetch_related('records') return qs.select_related('account').prefetch_related('records')
@admin.register(Domain)
class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = ( list_display = (
'structured_name', 'display_is_top', 'display_websites', 'display_addresses', 'account_link' 'structured_name', 'display_is_top', 'display_websites', 'display_addresses', 'account_link'
@ -74,27 +71,19 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
top_link = admin_link('top') top_link = admin_link('top')
@admin.display(
description=_("name"),
ordering='structured_name',
)
def structured_name(self, domain): def structured_name(self, domain):
if domain.is_top: if domain.is_top:
return domain.name return domain.name
return mark_safe('&nbsp;'*4 + domain.name) return mark_safe('&nbsp;'*4 + domain.name)
structured_name.short_description = _("name")
structured_name.admin_order_field = 'structured_name'
@admin.display(
description=_("Is top"),
boolean=True,
ordering='top',
)
def display_is_top(self, domain): def display_is_top(self, domain):
return domain.is_top return domain.is_top
display_is_top.short_description = _("Is top")
display_is_top.boolean = True
display_is_top.admin_order_field = 'top'
@admin.display(
description=_("Websites"),
ordering='websites__name',
)
@mark_safe @mark_safe
def display_websites(self, domain): def display_websites(self, domain):
if apps.isinstalled('orchestra.contrib.websites'): if apps.isinstalled('orchestra.contrib.websites'):
@ -117,11 +106,9 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
) )
return _("No website %s") % (add_link) return _("No website %s") % (add_link)
return '---' return '---'
display_websites.admin_order_field = 'websites__name'
display_websites.short_description = _("Websites")
@admin.display(
description=_("Addresses"),
ordering='addresses__count',
)
@mark_safe @mark_safe
def display_addresses(self, domain): def display_addresses(self, domain):
if apps.isinstalled('orchestra.contrib.mailboxes'): if apps.isinstalled('orchestra.contrib.mailboxes'):
@ -139,10 +126,9 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
return '<a href="%s" title="%s">%s</a> %s' % (url, title, len(addresses), add_link) return '<a href="%s" title="%s">%s</a> %s' % (url, title, len(addresses), add_link)
return _("No address %s") % (add_link) return _("No address %s") % (add_link)
return '---' return '---'
display_addresses.short_description = _("Addresses")
display_addresses.admin_order_field = 'addresses__count'
@admin.display(
description=_("Implicit records")
)
@mark_safe @mark_safe
def implicit_records(self, domain): def implicit_records(self, domain):
types = set(domain.records.values_list('type', flat=True)) types = set(domain.records.values_list('type', flat=True))
@ -162,6 +148,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
else: else:
lines.append(line) lines.append(line)
return '<br>'.join(lines) return '<br>'.join(lines)
implicit_records.short_description = _("Implicit records")
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
""" Add SOA fields when domain is top """ """ Add SOA fields when domain is top """
@ -237,3 +224,4 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
self.save_formset(request, form, formset, change) self.save_formset(request, form, formset, change)
admin.site.register(Domain, DomainAdmin)

View File

@ -0,0 +1,462 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-06 07:58+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:25
#, python-format
msgid "%s zone content"
msgstr "%s contingut zona"
#: actions.py:29 templates/admin/domains/domain/change_form.html:7
#: templates/admin/domains/domain/view_zone.html:12
msgid "View zone"
msgstr "Veure zona"
#: actions.py:53
msgid ""
"This subdomain was not explicitly selected but has been automatically added "
"to this list."
msgstr ""
"Aquest subdomini no va ser seleccionat explícitament, però s'ha afegit automàticament"
"A aquesta llista."
#: actions.py:88
msgid "Records for one selected domain have been updated."
msgstr "S'han actualitzat els registres d'un domini seleccionat."
#: actions.py:89
#, python-format
msgid "Records for %i selected domains have been updated."
msgstr "S'han actualitzat els registres dels dominis seleccionars %i "
#: actions.py:96 actions.py:97
msgid "Edit records"
msgstr "Editar registres"
#: actions.py:112
msgid "Set SOA on subdomains is not possible."
msgstr "Configurar SOA en subdominis no és possible."
#: actions.py:120
#, python-format
msgid "SOA set %s"
msgstr ""
#: actions.py:131
msgid "SOA record for one domain has been updated."
msgstr "El registre SOA per a un domini s'ha actualitzat."
#: actions.py:132
#, python-format
msgid "SOA record for %s domains has been updated."
msgstr "S'ha actualitzat el rècord SOA per als dominis de %s"
#: actions.py:139 actions.py:152
msgid "Set SOA for selected domains"
msgstr "Configurar SOA pels dominis seleccionats"
#: actions.py:141
msgid "Set SOA"
msgstr "Afegir SOA"
#: admin.py:28
msgid "Extra records"
msgstr "Registres Extra"
#: admin.py:36
msgid "Subdomains"
msgstr "Subdominis"
#: admin.py:39
msgid "Name"
msgstr "Nom"
#: admin.py:44
msgid "Declared records"
msgstr "Registres declarats"
#: admin.py:78 models.py:27
msgid "name"
msgstr "nom"
#: admin.py:83
msgid "Is top"
msgstr ""
#: admin.py:96
msgid "Edit website"
msgstr "Editar website"
#: admin.py:105
msgid "Add website"
msgstr "Crear website"
#: admin.py:107
#, python-format
msgid "No website %s"
msgstr ""
#: admin.py:110
msgid "Websites"
msgstr ""
#: admin.py:119
msgid "Add address"
msgstr ""
#: admin.py:127
#, python-format
msgid "No address %s"
msgstr ""
#: admin.py:129
msgid "Addresses"
msgstr ""
#: admin.py:151 admin.py:158
msgid "Implicit records"
msgstr "Registres implicits"
#: admin.py:165
msgid "SOA"
msgstr ""
#: admin.py:168
msgid ""
"SOA (Start of Authority) records are used to determine how the zone "
"propagates to the secondary nameservers."
msgstr ""
"Els registres SOA (Start of Authority) s'utilitzen per determinar com la zona"
"Es propaga als servidors de noms secundaris."
#: backends.py:22
msgid "Bind9 master domain"
msgstr ""
#: backends.py:184
msgid "Bind9 slave domain"
msgstr ""
#: filters.py:7
msgid "top domains"
msgstr ""
#: filters.py:12
msgid "Top domains"
msgstr ""
#: filters.py:22
msgid "has websites"
msgstr ""
#: filters.py:27
msgid "True"
msgstr ""
#: filters.py:28
msgid "False"
msgstr ""
#: filters.py:41
msgid "has addresses"
msgstr ""
#: forms.py:14
msgid "Names"
msgstr ""
#: forms.py:15
msgid ""
"Fully qualified domain name per line. All domains will have the provided "
"account and records."
msgstr ""
"Nom de domini completament qualificat per línia. Tots els dominis tindran el subministrament"
"Compte i registres."
#: forms.py:29
#, python-format
msgid "%s domain name provided multiple times."
msgstr "El nom de domini %s es va proporcionar diverses vegades."
#: forms.py:65
msgid "An account should be provided for top domain names."
msgstr ""
#: forms.py:72
msgid "Provided domain names belong to different accounts."
msgstr "Els noms de domini pertanyen a diferents comptes"
#: forms.py:118
#, python-format
msgid ""
"%s: Hosts can not have underscore character '_', consider providing a SRV, "
"CNAME or TXT record."
msgstr ""
"%s: Els Hosts no poden tenir un caràcter '_', considereu proporcionar un SRV"
"Registre CNAME o TXT."
#: forms.py:142
msgid "Clear refresh"
msgstr ""
#: forms.py:143
msgid "Remove custom refresh value for all selected domains."
msgstr ""
#: forms.py:145
msgid "Clear retry"
msgstr ""
#: forms.py:146
msgid "Remove custom retry value for all selected domains."
msgstr ""
#: forms.py:148
msgid "Clear expire"
msgstr ""
#: forms.py:149
msgid "Remove custom expire value for all selected domains."
msgstr ""
#: forms.py:151
msgid "Clear min TTL"
msgstr ""
#: forms.py:152
msgid "Remove custom min TTL value for all selected domains."
msgstr ""
#: models.py:28
msgid "Domain or subdomain name."
msgstr "nom de Domini o Subdomini"
#: models.py:33
msgid "Account"
msgstr "Compte"
#: models.py:34
msgid "Automatically selected for subdomains."
msgstr "Seleccionat automàticament per a subdominis."
#: models.py:36
msgid "top domain"
msgstr ""
#: models.py:37
msgid "serial"
msgstr ""
#: models.py:38
msgid "A revision number that changes whenever this domain is updated."
msgstr "Un número de revisió que canvia sempre que aquest domini s'actualitzi."
#: models.py:39
msgid "refresh"
msgstr ""
#: models.py:41
#, python-format
msgid ""
"The time a secondary DNS server waits before querying the primary DNS "
"server's SOA record to check for changes. When the refresh time expires, the "
"secondary DNS server requests a copy of the current SOA record from the "
"primary. The primary DNS server complies with this request. The secondary "
"DNS server compares the serial number of the primary DNS server's current "
"SOA record and the serial number in it's own SOA record. If they are "
"different, the secondary DNS server will request a zone transfer from the "
"primary DNS server. The default value is <tt>%s</tt>."
msgstr ""
"El temps que un servidor DNS secundari espera abans de consultar el DNS primari"
"Registre SOA del servidor per comprovar si hi ha canvis. Quan el temps d'actualització caduca,"
"El servidor DNS secundari sol·licita una còpia del registre SOA actual del"
"Primària. El servidor DNS primari compleix aquesta sol·licitud. La secundària"
"El servidor DNS compara el número de sèrie del corrent principal del servidor DNS"
"Registre SOA i el número de sèrie del seu propi registre SOA. Si ho són"
"Diferent, el servidor DNS secundari sol·licitarà una transferència de zona des del"
"Servidor DNS primari. El valor per defecte és <tt>%s</tt>."
#: models.py:50
msgid "retry"
msgstr ""
#: models.py:52
#, python-format
msgid ""
"The time a secondary server waits before retrying a failed zone transfer. "
"Normally, the retry time is less than the refresh time. The default value is "
"<tt>%s</tt>."
msgstr ""
"El temps que un servidor secundari espera abans de tornar a tornar a transferir una zona fallida."
"Normalment, el temps de tornada és inferior al temps d'actualització. El valor predeterminat és"
"<tt>%s</tt>."
#: models.py:55
msgid "expire"
msgstr ""
#: models.py:57
#, python-format
msgid ""
"The time that a secondary server will keep trying to complete a zone "
"transfer. If this time expires prior to a successful zone transfer, the "
"secondary server will expire its zone file. This means the secondary will "
"stop answering queries. The default value is <tt>%s</tt>."
msgstr ""
"El temps que un servidor secundari continuarà intentant completar una zona"
"Transferència. Si aquesta vegada caduca abans d'una transferència de zona amb èxit,"
"El servidor secundari caducarà el seu fitxer de zona. Això significa que la voluntat secundària"
"Deixa de respondre a les consultes. El valor predeterminat és <tt>%s</tt>."
#: models.py:62
msgid "min TTL"
msgstr ""
#: models.py:64
#, python-format
msgid ""
"The minimum time-to-live value applies to all resource records in the zone "
"file. This value is supplied in query responses to inform other servers how "
"long they should keep the data in cache. The default value is <tt>%s</tt>."
msgstr ""
"El valor mínim de temps en viu s'aplica a tots els registres de recursos de la zona"
"Fitxer. Aquest valor es subministra a les respostes de consulta per informar a altres servidors com"
"Long han de mantenir les dades en memòria cau. El valor predeterminat és <tt>%s</tt>"
#: models.py:118
msgid "top domain with one subdomain"
msgstr ""
#: models.py:119
#, python-format
msgid "top domain with %d subdomains"
msgstr ""
#: models.py:121
msgid "subdomain"
msgstr "subdomini"
#: models.py:308
msgid "A (IPv4 address)"
msgstr ""
#: models.py:309
msgid "AAAA (IPv6 address)"
msgstr ""
#: models.py:329
msgid "domain"
msgstr "domini"
#: models.py:330
msgid "TTL"
msgstr ""
#: models.py:331
#, python-format
msgid "Record TTL, defaults to %s"
msgstr "Registre TTL, per defecte a %s"
#: models.py:333
msgid "type"
msgstr "tipus"
#: models.py:335
msgid "value"
msgstr "valor"
#: models.py:336
msgid "MX, NS and CNAME records sould end with a dot."
msgstr "Els registres de MX, NS i CNAME s'acaben amb un punt."
#: serializers.py:36
msgid "Can not create subdomains of other users domains"
msgstr "No es pot crear subdominis d'altres dominis d'usuaris"
#: templates/admin/domains/domain/change_form.html:11
msgid "History"
msgstr "Historial"
#: templates/admin/domains/domain/change_form.html:13
msgid "View on site"
msgstr "Veure al lloc"
#: templates/admin/domains/domain/view_zone.html:8
msgid "Home"
msgstr ""
#: validators.py:28
msgid "This domain name is not allowed"
msgstr "Aquest nom de domini no està permès"
#: validators.py:37
msgid "Not a valid domain name."
msgstr "No és un nom de domini vàlid."
#: validators.py:46
#, python-format
msgid "%s is not an appropiate zone interval value"
msgstr "%s no és un valor d'interval de zona adequat"
#: validators.py:57
msgid ""
"Labels must start and end with a letter or digit, and have as interior "
"characters only letters, digits, and hyphen."
msgstr ""
"Les etiquetes han de començar i acabar amb una lletra o dígit i tenir en l'interior"
"Només caràcters lletres, dígits i guionet."
#: validators.py:61
msgid "Use a fully expanded domain name ending with a dot."
msgstr "Utilitzeu un nom de domini complet amb un punt final."
#: validators.py:64
msgid "Labels must be 63 characters or less."
msgstr "Les etiquetes han de tenir 63 caràcters o menys."
#: validators.py:68
msgid ""
"MX record format is 'priority domain.' tuple, with priority being a number."
msgstr ""
"El format de registre MX és 'domini prioritari'. Tuple, amb la prioritat que és un número "
#: validators.py:83 validators.py:95
#, python-format
msgid "%s is not an appropiate SRV record value"
msgstr "%s no és un valor de registre SRV Apropiat"
#: validators.py:111
#, python-format
msgid ""
"%s is not an appropiate CAA record value, sintax: 0-255 issue|issuewild|"
"iodef \"domain|mailto:email\""
msgstr ""
"%s no és un valor de registre CAA adequat, sintaxi: 0-255 issue|issuewild|"
"iodef \"domain|mailto:email\""
#: validators.py:134
msgid ""
"This record value contains spaces, you must enclose the string in double "
"quotes; otherwise, individual words will be separately quoted and break up "
"the record into multiple parts."
msgstr ""
"Aquest valor de registre conté espais, heu d'incloure la cadena ab cometes dobles"
"en cas contrari, les paraules individuals es citaran per separat i es trencaran"
"El registre en diverses parts"

View File

@ -0,0 +1,461 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-05 10:11+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:25
#, python-format
msgid "%s zone content"
msgstr "%s contenido de zona"
#: actions.py:29 templates/admin/domains/domain/change_form.html:7
#: templates/admin/domains/domain/view_zone.html:12
msgid "View zone"
msgstr "Ver zona"
#: actions.py:53
msgid ""
"This subdomain was not explicitly selected but has been automatically added "
"to this list."
msgstr ""
"Este subdominio no fue seleccionado explícitamente, pero se ha agregado automáticamente"
"A esta lista"
#: actions.py:88
msgid "Records for one selected domain have been updated."
msgstr "Se han actualizado los registros del dominio seleccionado"
#: actions.py:89
#, python-format
msgid "Records for %i selected domains have been updated."
msgstr "Se han actualizado los registros para los %i dominios seleccionados"
#: actions.py:96 actions.py:97
msgid "Edit records"
msgstr "Editar Registros"
#: actions.py:112
msgid "Set SOA on subdomains is not possible."
msgstr "No es posible añadir el registro SOA en los subdominios"
#: actions.py:120
#, python-format
msgid "SOA set %s"
msgstr "Añadir SOA %s"
#: actions.py:131
msgid "SOA record for one domain has been updated."
msgstr "Se ha actualizado el registro SOA del dominio"
#: actions.py:132
#, python-format
msgid "SOA record for %s domains has been updated."
msgstr "Se actualizo el registro SOA para los dominios %s"
#: actions.py:139 actions.py:152
msgid "Set SOA for selected domains"
msgstr "Añadir SOA en los dominios seleccionados"
#: actions.py:141
msgid "Set SOA"
msgstr "Añadir SOA"
#: admin.py:28
msgid "Extra records"
msgstr "Registros extra"
#: admin.py:36
msgid "Subdomains"
msgstr "Subdominios"
#: admin.py:39
msgid "Name"
msgstr "Nombre"
#: admin.py:44
msgid "Declared records"
msgstr "Registros declarados"
#: admin.py:78 models.py:27
msgid "name"
msgstr "Nombre"
#: admin.py:83
msgid "Is top"
msgstr "Es top level"
#: admin.py:96
msgid "Edit website"
msgstr "Editar WebSite"
#: admin.py:105
msgid "Add website"
msgstr "Añadir WebSite"
#: admin.py:107
#, python-format
msgid "No website %s"
msgstr ""
#: admin.py:110
msgid "Websites"
msgstr ""
#: admin.py:119
msgid "Add address"
msgstr "Añadir dirección"
#: admin.py:127
#, python-format
msgid "No address %s"
msgstr ""
#: admin.py:129
msgid "Addresses"
msgstr "Direcciones"
#: admin.py:151 admin.py:158
msgid "Implicit records"
msgstr "Registros implicitos"
#: admin.py:165
msgid "SOA"
msgstr ""
#: admin.py:168
msgid ""
"SOA (Start of Authority) records are used to determine how the zone "
"propagates to the secondary nameservers."
msgstr ""
"Los registros SOA (inicio de la autoridad) se utilizan para determinar cómo la zona"
"Se propaga a los servidores de nombres secundarios"
#: backends.py:22
msgid "Bind9 master domain"
msgstr ""
#: backends.py:184
msgid "Bind9 slave domain"
msgstr ""
#: filters.py:7
msgid "top domains"
msgstr ""
#: filters.py:12
msgid "Top domains"
msgstr ""
#: filters.py:22
msgid "has websites"
msgstr ""
#: filters.py:27
msgid "True"
msgstr ""
#: filters.py:28
msgid "False"
msgstr ""
#: filters.py:41
msgid "has addresses"
msgstr ""
#: forms.py:14
msgid "Names"
msgstr "Nombres"
#: forms.py:15
msgid ""
"Fully qualified domain name per line. All domains will have the provided "
"account and records."
msgstr ""
"Nombre de dominio totalmente calificado por línea. Todos los dominios tendrán los proporcionados"
"Cuenta y registros"
#: forms.py:29
#, python-format
msgid "%s domain name provided multiple times."
msgstr "%s Nombre de dominio proporcionado varias veces."
#: forms.py:65
msgid "An account should be provided for top domain names."
msgstr ""
#: forms.py:72
msgid "Provided domain names belong to different accounts."
msgstr "Los nombres de dominio proporcionados pertenecen a diferentes cuentas"
#: forms.py:118
#, python-format
msgid ""
"%s: Hosts can not have underscore character '_', consider providing a SRV, "
"CNAME or TXT record."
msgstr ""
"%s: los hosts no pueden tener un carácter subrayado '_', considere proporcionar un SRV, "
"Registro CNAME o TXT."
#: forms.py:142
msgid "Clear refresh"
msgstr ""
#: forms.py:143
msgid "Remove custom refresh value for all selected domains."
msgstr ""
#: forms.py:145
msgid "Clear retry"
msgstr ""
#: forms.py:146
msgid "Remove custom retry value for all selected domains."
msgstr ""
#: forms.py:148
msgid "Clear expire"
msgstr ""
#: forms.py:149
msgid "Remove custom expire value for all selected domains."
msgstr ""
#: forms.py:151
msgid "Clear min TTL"
msgstr ""
#: forms.py:152
msgid "Remove custom min TTL value for all selected domains."
msgstr ""
#: models.py:28
msgid "Domain or subdomain name."
msgstr "Dominio o Subdominio"
#: models.py:33
msgid "Account"
msgstr "Cuenta"
#: models.py:34
msgid "Automatically selected for subdomains."
msgstr "Seleccionado automáticamente para subdominios"
#: models.py:36
msgid "top domain"
msgstr ""
#: models.py:37
msgid "serial"
msgstr ""
#: models.py:38
msgid "A revision number that changes whenever this domain is updated."
msgstr "Un número de revisión que cambia cada vez que se actualiza este dominio"
#: models.py:39
msgid "refresh"
msgstr "actualizar"
#: models.py:41
#, python-format
msgid ""
"The time a secondary DNS server waits before querying the primary DNS "
"server's SOA record to check for changes. When the refresh time expires, the "
"secondary DNS server requests a copy of the current SOA record from the "
"primary. The primary DNS server complies with this request. The secondary "
"DNS server compares the serial number of the primary DNS server's current "
"SOA record and the serial number in it's own SOA record. If they are "
"different, the secondary DNS server will request a zone transfer from the "
"primary DNS server. The default value is <tt>%s</tt>."
msgstr ""
"La hora en que un servidor DNS secundario espera antes de consultar el DNS primario"
"El registro SOA del servidor para verificar los cambios. Cuando la tiempo de actualización expira,"
"El servidor DNS secundario solicita una copia del registro SOA actual del"
"Primario. El servidor DNS principal cumple con esta solicitud. El secundario"
"El servidor DNS compara el número de serie del servidor DNS principal actual"
"SOA Record y el número de serie en su propio registro SOA. Si lo son"
"Diferente, el servidor DNS secundario solicitará una transferencia de zona del"
"Servidor DNS primario. El valor predeterminado es <tt>%s</tt>"
#: models.py:50
msgid "retry"
msgstr ""
#: models.py:52
#, python-format
msgid ""
"The time a secondary server waits before retrying a failed zone transfer. "
"Normally, the retry time is less than the refresh time. The default value is "
"<tt>%s</tt>."
msgstr ""
"El tiempo en que un servidor secundario espera antes de volver a intentar una transferencia de zona fallida"
"Normalmente, el tiempo de reintento es menor que el tiempo de actualización. El valor predeterminado es"
"<Tt>%s</tt>"
#: models.py:55
msgid "expire"
msgstr ""
#: models.py:57
#, python-format
msgid ""
"The time that a secondary server will keep trying to complete a zone "
"transfer. If this time expires prior to a successful zone transfer, the "
"secondary server will expire its zone file. This means the secondary will "
"stop answering queries. The default value is <tt>%s</tt>."
msgstr ""
"El momento en que un servidor secundario seguirá intentando completar una zona"
"Transferencia. Si este tiempo expira antes de una transferencia de zona exitosa,"
"El servidor secundario caducará su archivo de zona. Esto significa que el secundario lo hará"
"Deje de responder consultas. El valor predeterminado es <tt>%s</tt>"
#: models.py:62
msgid "min TTL"
msgstr ""
#: models.py:64
#, python-format
msgid ""
"The minimum time-to-live value applies to all resource records in the zone "
"file. This value is supplied in query responses to inform other servers how "
"long they should keep the data in cache. The default value is <tt>%s</tt>."
msgstr ""
"El valor mínimo de tiempo de vida se aplica a todos los registros de recursos en la zona"
"Archivo. Este valor se suministra en las respuestas de consulta para informar a otros servidores cómo"
"Durante mucho tiempo deberían mantener los datos en caché. El valor predeterminado es <tt>%s </tt>"
#: models.py:118
msgid "top domain with one subdomain"
msgstr ""
#: models.py:119
#, python-format
msgid "top domain with %d subdomains"
msgstr ""
#: models.py:121
msgid "subdomain"
msgstr "Subdominio"
#: models.py:308
msgid "A (IPv4 address)"
msgstr ""
#: models.py:309
msgid "AAAA (IPv6 address)"
msgstr ""
#: models.py:329
msgid "domain"
msgstr "dominio"
#: models.py:330
msgid "TTL"
msgstr ""
#: models.py:331
#, python-format
msgid "Record TTL, defaults to %s"
msgstr "Registro TTL, por defecto %s"
#: models.py:333
msgid "type"
msgstr "tipo"
#: models.py:335
msgid "value"
msgstr "valor"
#: models.py:336
msgid "MX, NS and CNAME records sould end with a dot."
msgstr "Los registros MX, NS and CNAME han de acabar con punto"
#: serializers.py:36
msgid "Can not create subdomains of other users domains"
msgstr "No puede crear subdominios de los dominios de otros usuarios"
#: templates/admin/domains/domain/change_form.html:11
msgid "History"
msgstr "Historial"
#: templates/admin/domains/domain/change_form.html:13
msgid "View on site"
msgstr "Ver en el sitio"
#: templates/admin/domains/domain/view_zone.html:8
msgid "Home"
msgstr ""
#: validators.py:28
msgid "This domain name is not allowed"
msgstr "Este nombre de dominio no esta permitido"
#: validators.py:37
msgid "Not a valid domain name."
msgstr "No es nombre de dominio valido"
#: validators.py:46
#, python-format
msgid "%s is not an appropiate zone interval value"
msgstr "%s no es un valor de intervalo de zona apropiado"
#: validators.py:57
msgid ""
"Labels must start and end with a letter or digit, and have as interior "
"characters only letters, digits, and hyphen."
msgstr ""
"Las etiquetas deben comenzar y terminar con una letra o dígito, y tener en su interior"
"Caracteres solo letras, dígitos y guiones"
#: validators.py:61
msgid "Use a fully expanded domain name ending with a dot."
msgstr "Use un nombre de dominio completo que termine con un punto"
#: validators.py:64
msgid "Labels must be 63 characters or less."
msgstr "Las etiquetas deben ser de 63 caracteres o menos"
#: validators.py:68
msgid ""
"MX record format is 'priority domain.' tuple, with priority being a number."
msgstr ""
"El formato de registro MX es 'dominio prioritario'. Tuple, con la prioridad siendo un número "
#: validators.py:83 validators.py:95
#, python-format
msgid "%s is not an appropiate SRV record value"
msgstr "%s no es un valor de SRV apropiado"
#: validators.py:111
#, python-format
msgid ""
"%s is not an appropiate CAA record value, sintax: 0-255 issue|issuewild|"
"iodef \"domain|mailto:email\""
msgstr ""
"%s no es un valor de registro de CAA apropiado, sintaxis: 0-255 issue|issuewild|"
"iodef \"domain|mailto:email\""
#: validators.py:134
msgid ""
"This record value contains spaces, you must enclose the string in double "
"quotes; otherwise, individual words will be separately quoted and break up "
"the record into multiple parts."
msgstr ""
"Este valor de registro contiene espacios, debe encerrar la cadena en el doble"
"Comillas; de lo contrario, las palabras individuales serán citadas por separado y se romperán"
"El registro en múltiples partes"

View File

@ -1,15 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:27
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import orchestra.contrib.domains.utils import orchestra.contrib.domains.utils
import orchestra.contrib.domains.validators import orchestra.contrib.domains.validators
from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
@ -18,21 +20,61 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Domain', name='Domain',
fields=[ fields=[
('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(unique=True, max_length=256, validators=[orchestra.contrib.domains.validators.validate_domain_name, orchestra.contrib.domains.validators.validate_allowed_domain], verbose_name='name', help_text='Domain or subdomain name.')), ('name', models.CharField(help_text='Domain or subdomain name.', max_length=256, unique=True, validators=[orchestra.contrib.domains.validators.validate_domain_name, orchestra.contrib.domains.validators.validate_allowed_domain], verbose_name='name')),
('serial', models.IntegerField(default=orchestra.contrib.domains.utils.generate_zone_serial, verbose_name='serial', help_text='Serial number')), ('serial', models.IntegerField(default=orchestra.contrib.domains.utils.generate_zone_serial, help_text='Serial number', verbose_name='serial')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='domains', help_text='Automatically selected for subdomains.', to=settings.AUTH_USER_MODEL, verbose_name='Account', blank=True)), ('account', models.ForeignKey(blank=True, help_text='Automatically selected for subdomains.', on_delete=django.db.models.deletion.CASCADE, related_name='domains', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
('top', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, null=True, to='domains.Domain', editable=False, related_name='subdomain_set')), ('top', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subdomain_set', to='domains.Domain')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Record', name='Record',
fields=[ fields=[
('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ttl', models.CharField(help_text='Record TTL, defaults to 1h', max_length=8, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='TTL', blank=True)), ('ttl', models.CharField(blank=True, help_text='Record TTL, defaults to 1h', max_length=8, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='TTL')),
('type', models.CharField(max_length=32, verbose_name='type', choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SOA', 'SOA')])), ('type', models.CharField(choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SPF', 'SPF')], max_length=32, verbose_name='type')),
('value', models.CharField(max_length=256, verbose_name='value')), ('value', models.CharField(help_text='MX, NS and CNAME records sould end with a dot.', max_length=1024, verbose_name='value')),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='domains.Domain', verbose_name='domain')), ('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='domains.Domain', verbose_name='domain')),
], ],
), ),
migrations.AlterField(
model_name='domain',
name='serial',
field=models.IntegerField(default=orchestra.contrib.domains.utils.generate_zone_serial, editable=False, help_text='A revision number that changes whenever this domain is updated.', verbose_name='serial'),
),
migrations.AddField(
model_name='domain',
name='expire',
field=models.CharField(blank=True, help_text='The time that a secondary server will keep trying to complete a zone transfer. If this time expires prior to a successful zone transfer, the secondary server will expire its zone file. This means the secondary will stop answering queries. The default value is <tt>4w</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='expire'),
),
migrations.AddField(
model_name='domain',
name='min_ttl',
field=models.CharField(blank=True, help_text='The minimum time-to-live value applies to all resource records in the zone file. This value is supplied in query responses to inform other servers how long they should keep the data in cache. The default value is <tt>1h</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='min TTL'),
),
migrations.AddField(
model_name='domain',
name='refresh',
field=models.CharField(blank=True, help_text="The time a secondary DNS server waits before querying the primary DNS server's SOA record to check for changes. When the refresh time expires, the secondary DNS server requests a copy of the current SOA record from the primary. The primary DNS server complies with this request. The secondary DNS server compares the serial number of the primary DNS server's current SOA record and the serial number in it's own SOA record. If they are different, the secondary DNS server will request a zone transfer from the primary DNS server. The default value is <tt>1d</tt>.", max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='refresh'),
),
migrations.AddField(
model_name='domain',
name='retry',
field=models.CharField(blank=True, help_text='The time a secondary server waits before retrying a failed zone transfer. Normally, the retry time is less than the refresh time. The default value is <tt>2h</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='retry'),
),
migrations.AlterField(
model_name='domain',
name='name',
field=models.CharField(db_index=True, help_text='Domain or subdomain name.', max_length=256, unique=True, validators=[orchestra.contrib.domains.validators.validate_domain_name, orchestra.contrib.domains.validators.validate_allowed_domain], verbose_name='name'),
),
migrations.AlterField(
model_name='domain',
name='top',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subdomain_set', to='domains.Domain', verbose_name='top domain'),
),
migrations.AddField(
model_name='domain',
name='dns2136_address_match_list',
field=models.CharField(blank=True, default='key pangea.key;', help_text="A bind-9 'address_match_list' that will be granted permission to perform dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.", max_length=80),
),
] ]

View File

@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:27
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import orchestra.contrib.domains.utils
import orchestra.contrib.domains.validators
class Migration(migrations.Migration):
replaces = [('domains', '0001_initial'), ('domains', '0002_auto_20150715_1017'), ('domains', '0003_auto_20150720_1121'), ('domains', '0004_auto_20150720_1121'), ('domains', '0005_auto_20160219_1034'), ('domains', '0006_auto_20170528_2011'), ('domains', '0007_auto_20190805_1134'), ('domains', '0008_domain_dns2136_address_match_list'), ('domains', '0009_auto_20200204_1217'), ('domains', '0010_auto_20210330_1049')]
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Domain',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Domain or subdomain name.', max_length=256, unique=True, validators=[orchestra.contrib.domains.validators.validate_domain_name, orchestra.contrib.domains.validators.validate_allowed_domain], verbose_name='name')),
('serial', models.IntegerField(default=orchestra.contrib.domains.utils.generate_zone_serial, help_text='Serial number', verbose_name='serial')),
('account', models.ForeignKey(blank=True, help_text='Automatically selected for subdomains.', on_delete=django.db.models.deletion.CASCADE, related_name='domains', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
('top', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subdomain_set', to='domains.Domain')),
],
),
migrations.CreateModel(
name='Record',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ttl', models.CharField(blank=True, help_text='Record TTL, defaults to 1h', max_length=8, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='TTL')),
('type', models.CharField(choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SPF', 'SPF')], max_length=32, verbose_name='type')),
('value', models.CharField(help_text='MX, NS and CNAME records sould end with a dot.', max_length=1024, verbose_name='value')),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='domains.Domain', verbose_name='domain')),
],
),
migrations.AlterField(
model_name='domain',
name='serial',
field=models.IntegerField(default=orchestra.contrib.domains.utils.generate_zone_serial, editable=False, help_text='A revision number that changes whenever this domain is updated.', verbose_name='serial'),
),
migrations.AddField(
model_name='domain',
name='expire',
field=models.CharField(blank=True, help_text='The time that a secondary server will keep trying to complete a zone transfer. If this time expires prior to a successful zone transfer, the secondary server will expire its zone file. This means the secondary will stop answering queries. The default value is <tt>4w</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='expire'),
),
migrations.AddField(
model_name='domain',
name='min_ttl',
field=models.CharField(blank=True, help_text='The minimum time-to-live value applies to all resource records in the zone file. This value is supplied in query responses to inform other servers how long they should keep the data in cache. The default value is <tt>1h</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='min TTL'),
),
migrations.AddField(
model_name='domain',
name='refresh',
field=models.CharField(blank=True, help_text="The time a secondary DNS server waits before querying the primary DNS server's SOA record to check for changes. When the refresh time expires, the secondary DNS server requests a copy of the current SOA record from the primary. The primary DNS server complies with this request. The secondary DNS server compares the serial number of the primary DNS server's current SOA record and the serial number in it's own SOA record. If they are different, the secondary DNS server will request a zone transfer from the primary DNS server. The default value is <tt>1d</tt>.", max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='refresh'),
),
migrations.AddField(
model_name='domain',
name='retry',
field=models.CharField(blank=True, help_text='The time a secondary server waits before retrying a failed zone transfer. Normally, the retry time is less than the refresh time. The default value is <tt>2h</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='retry'),
),
migrations.AlterField(
model_name='domain',
name='name',
field=models.CharField(db_index=True, help_text='Domain or subdomain name.', max_length=256, unique=True, validators=[orchestra.contrib.domains.validators.validate_domain_name, orchestra.contrib.domains.validators.validate_allowed_domain], verbose_name='name'),
),
migrations.AlterField(
model_name='domain',
name='top',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subdomain_set', to='domains.Domain', verbose_name='top domain'),
),
migrations.AddField(
model_name='domain',
name='dns2136_address_match_list',
field=models.CharField(blank=True, default='key pangea.key;', help_text="A bind-9 'address_match_list' that will be granted permission to perform dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.", max_length=80),
),
]

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('domains', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='domain',
name='expire',
field=models.IntegerField(null=True, blank=True, help_text='The upper limit in seconds before a zone is considered no longer authoritative (4w by default).', verbose_name='expire'),
),
migrations.AddField(
model_name='domain',
name='min_ttl',
field=models.IntegerField(null=True, blank=True, help_text='The negative result TTL (for example, how long a resolver should consider a negative result for a subdomain to be valid before retrying) (1h by default).', verbose_name='refresh'),
),
migrations.AddField(
model_name='domain',
name='refresh',
field=models.IntegerField(null=True, blank=True, help_text='The number of seconds before the zone should be refreshed (1d by default).', verbose_name='refresh'),
),
migrations.AddField(
model_name='domain',
name='retry',
field=models.IntegerField(null=True, blank=True, help_text='The number of seconds before a failed refresh should be retried (2h by default).', verbose_name='retry'),
),
migrations.AlterField(
model_name='record',
name='value',
field=models.CharField(max_length=256, help_text='MX, NS and CNAME records sould end with a dot.', verbose_name='value'),
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # Generated by Django 2.2.24 on 2024-07-11 12:25
# Generated by Django 1.10.5 on 2017-05-28 18:11
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
import orchestra.contrib.domains.validators import orchestra.contrib.domains.validators
@ -9,7 +7,7 @@ import orchestra.contrib.domains.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('domains', '0005_auto_20160219_1034'), ('domains', '0001_initial'),
] ]
operations = [ operations = [
@ -26,6 +24,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='record', model_name='record',
name='type', name='type',
field=models.CharField(choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SPF', 'SPF')], max_length=32, verbose_name='type'), field=models.CharField(choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SPF', 'SPF'), ('CAA', 'CAA')], max_length=32, verbose_name='type'),
), ),
] ]

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.contrib.domains.utils
class Migration(migrations.Migration):
dependencies = [
('domains', '0002_auto_20150715_1017'),
]
operations = [
migrations.RemoveField(
model_name='domain',
name='expire',
),
migrations.RemoveField(
model_name='domain',
name='min_ttl',
),
migrations.RemoveField(
model_name='domain',
name='refresh',
),
migrations.RemoveField(
model_name='domain',
name='retry',
),
migrations.AlterField(
model_name='domain',
name='serial',
field=models.IntegerField(editable=False, verbose_name='serial', default=orchestra.contrib.domains.utils.generate_zone_serial, help_text='A revision number that changes whenever this domain is updated.'),
),
]

View File

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.contrib.domains.validators
class Migration(migrations.Migration):
dependencies = [
('domains', '0003_auto_20150720_1121'),
]
operations = [
migrations.AddField(
model_name='domain',
name='expire',
field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text='The time that a secondary server will keep trying to complete a zone transfer. If this time expires prior to a successful zone transfer, the secondary server will expire its zone file. This means the secondary will stop answering queries. The default value is <tt>4w</tt>.', verbose_name='expire', max_length=16),
),
migrations.AddField(
model_name='domain',
name='min_ttl',
field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text='The minimum time-to-live value applies to all resource records in the zone file. This value is supplied in query responses to inform other servers how long they should keep the data in cache. The default value is <tt>1h</tt>.', verbose_name='min TTL', max_length=16),
),
migrations.AddField(
model_name='domain',
name='refresh',
field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text="The time a secondary DNS server waits before querying the primary DNS server's SOA record to check for changes. When the refresh time expires, the secondary DNS server requests a copy of the current SOA record from the primary. The primary DNS server complies with this request. The secondary DNS server compares the serial number of the primary DNS server's current SOA record and the serial number in it's own SOA record. If they are different, the secondary DNS server will request a zone transfer from the primary DNS server. The default value is <tt>1d</tt>.", verbose_name='refresh', max_length=16),
),
migrations.AddField(
model_name='domain',
name='retry',
field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text='The time a secondary server waits before retrying a failed zone transfer. Normally, the retry time is less than the refresh time. The default value is <tt>2h</tt>.', verbose_name='retry', max_length=16),
),
]

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import orchestra.contrib.domains.validators
class Migration(migrations.Migration):
dependencies = [
('domains', '0004_auto_20150720_1121'),
]
operations = [
migrations.AlterField(
model_name='domain',
name='name',
field=models.CharField(max_length=256, validators=[orchestra.contrib.domains.validators.validate_domain_name, orchestra.contrib.domains.validators.validate_allowed_domain], db_index=True, verbose_name='name', unique=True, help_text='Domain or subdomain name.'),
),
migrations.AlterField(
model_name='domain',
name='top',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, editable=False, verbose_name='top domain', related_name='subdomain_set', to='domains.Domain', null=True),
),
migrations.AlterField(
model_name='record',
name='type',
field=models.CharField(max_length=32, verbose_name='type', choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SPF', 'SPF'), ('SOA', 'SOA')]),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2019-08-05 09:34
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0006_auto_20170528_2011'),
]
operations = [
migrations.AlterField(
model_name='record',
name='value',
field=models.CharField(help_text='MX, NS and CNAME records sould end with a dot.', max_length=1024, verbose_name='value'),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2019-09-20 07:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0007_auto_20190805_1134'),
]
operations = [
migrations.AddField(
model_name='domain',
name='dns2136_address_match_list',
field=models.CharField(blank=True, default='none;', help_text="A bind-9 'address_match_list' that will be granted permission to perform dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.", max_length=80),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2020-02-04 11:17
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0008_domain_dns2136_address_match_list'),
]
operations = [
migrations.AlterField(
model_name='domain',
name='dns2136_address_match_list',
field=models.CharField(blank=True, default='key pangea.key;', help_text="A bind-9 'address_match_list' that will be granted permission to perform dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.", max_length=80),
),
]

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-03-30 10:49
from __future__ import unicode_literals
from django.db import migrations, models
import orchestra.contrib.domains.validators
class Migration(migrations.Migration):
dependencies = [
('domains', '0009_auto_20200204_1217'),
]
operations = [
migrations.AlterField(
model_name='domain',
name='min_ttl',
field=models.CharField(blank=True, help_text='The minimum time-to-live value applies to all resource records in the zone file. This value is supplied in query responses to inform other servers how long they should keep the data in cache. The default value is <tt>1h</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='min TTL'),
),
migrations.AlterField(
model_name='record',
name='ttl',
field=models.CharField(blank=True, help_text='Record TTL, defaults to 1h', max_length=8, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='TTL'),
),
]

View File

@ -145,8 +145,9 @@ class Domain(models.Model):
else: else:
zone += subdomain.render_records() zone += subdomain.render_records()
###darmengo 2021-03-25 add autoconfig ###darmengo 2021-03-25 add autoconfig
# TODO: que se asigne esta ip automaticamente
if self.has_default_mx(): if self.has_default_mx():
zone += 'autoconfig.{}. 30m IN A 109.69.8.133\n'.format(self.name) zone += 'autoconfig.{}. 30m IN A 45.150.186.197\n'.format(self.name)
###END darmengo 2021-03-25 add autoconfig ###END darmengo 2021-03-25 add autoconfig
for subdomain in sorted(tail, key=lambda x: len(x.name), reverse=True): for subdomain in sorted(tail, key=lambda x: len(x.name), reverse=True):
zone += subdomain.render_records() zone += subdomain.render_records()
@ -298,6 +299,7 @@ class Record(models.Model):
TXT = 'TXT' TXT = 'TXT'
SPF = 'SPF' SPF = 'SPF'
SOA = 'SOA' SOA = 'SOA'
CAA = 'CAA'
TYPE_CHOICES = ( TYPE_CHOICES = (
(MX, "MX"), (MX, "MX"),
@ -308,6 +310,7 @@ class Record(models.Model):
(SRV, "SRV"), (SRV, "SRV"),
(TXT, "TXT"), (TXT, "TXT"),
(SPF, "SPF"), (SPF, "SPF"),
(CAA, "CAA"),
) )
VALIDATORS = { VALIDATORS = {
@ -320,6 +323,7 @@ class Record(models.Model):
SPF: (validate_ascii, validators.validate_quoted_record), SPF: (validate_ascii, validators.validate_quoted_record),
SRV: (validators.validate_srv_record,), SRV: (validators.validate_srv_record,),
SOA: (validators.validate_soa_record,), SOA: (validators.validate_soa_record,),
CAA: (validators.validate_caa_record,),
} }
domain = models.ForeignKey(Domain, verbose_name=_("domain"), related_name='records', on_delete=models.CASCADE) domain = models.ForeignKey(Domain, verbose_name=_("domain"), related_name='records', on_delete=models.CASCADE)

View File

@ -53,7 +53,7 @@ def validate_zone_label(value):
Labels may not be all numbers, but may have a leading digit (e.g., 3com.com). Labels may not be all numbers, but may have a leading digit (e.g., 3com.com).
Labels must end and begin only with a letter or digit. See [RFC 1035] and [RFC 1123]. Labels must end and begin only with a letter or digit. See [RFC 1035] and [RFC 1123].
""" """
if not re.match(r'^[a-z0-9][\.\-0-9a-z]*[\.0-9a-z]$', value): if not re.match(r'^[a-z0-9][\.\-0-9a-z_]*[\.0-9a-z]$', value):
msg = _("Labels must start and end with a letter or digit, " msg = _("Labels must start and end with a letter or digit, "
"and have as interior characters only letters, digits, and hyphen.") "and have as interior characters only letters, digits, and hyphen.")
raise ValidationError(msg) raise ValidationError(msg)
@ -105,6 +105,28 @@ def validate_soa_record(value):
raise ValidationError(msg) raise ValidationError(msg)
def validate_caa_record(value):
# 0-255 issue|issuewild|iodef "domain|mailto:email"
# 0 issue "letsewncript.org"
msg = _("%s is not an appropiate CAA record value, sintax: 0-255 issue|issuewild|iodef \"domain|mailto:email\"") % value
values = value.split()
if len(values) != 3:
raise ValidationError(msg)
patron_flag = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
patron_tag = r'^(issue|issuewild|iodef)$'
patron_value_domain = r'^"[a-zA-Z0-9-.]+\.[a-zA-Z]+\.?"$'
patron_value_mailto = r'^"mailto:[a-zA-Z0-9.]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"$'
flag = re.match(patron_flag, values[0])
tag = re.match(patron_tag, values[1])
if values[1] == 'iodef':
valor = re.match(patron_value_mailto, values[2])
else:
valor = re.match(patron_value_domain, values[2])
if not (flag and tag and valor):
raise ValidationError(msg)
def validate_quoted_record(value): def validate_quoted_record(value):
value = value.strip() value = value.strip()
if ' ' in value and (value[0] != '"' or value[-1] != '"'): if ' ' in value and (value[0] != '"' or value[-1] != '"'):

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.history.apps.HistoryConfig'

View File

@ -36,10 +36,6 @@ class LogEntryAdmin(admin.ModelAdmin):
user_link = admin_link('user') user_link = admin_link('user')
display_action_time = admin_date('action_time', short_description=_("Time")) display_action_time = admin_date('action_time', short_description=_("Time"))
@admin.display(
description=_("Message"),
ordering='action_flag',
)
@mark_safe @mark_safe
def display_message(self, log): def display_message(self, log):
edit = format_html('<a href="{url}"><img src="{img}"></img></a>', **{ edit = format_html('<a href="{url}"><img src="{img}"></img></a>', **{
@ -62,22 +58,18 @@ class LogEntryAdmin(admin.ModelAdmin):
'object': log.object_repr, 'object': log.object_repr,
'edit': edit, 'edit': edit,
} }
display_message.short_description = _("Message")
display_message.admin_order_field = 'action_flag'
@admin.display(
description=_("Action"),
ordering='action_flag',
)
def display_action(self, log): def display_action(self, log):
if log.is_addition(): if log.is_addition():
return _("Added") return _("Added")
elif log.is_change(): elif log.is_change():
return _("Changed") return _("Changed")
return _("Deleted") return _("Deleted")
display_action.short_description = _("Action")
display_action.admin_order_field = 'action_flag'
@admin.display(
description=_("Content object"),
ordering='object_repr',
)
def content_object_link(self, log): def content_object_link(self, log):
ct = log.content_type ct = log.content_type
view = 'admin:%s_%s_change' % (ct.app_label, ct.model) view = 'admin:%s_%s_change' % (ct.app_label, ct.model)
@ -86,6 +78,8 @@ class LogEntryAdmin(admin.ModelAdmin):
except NoReverseMatch: except NoReverseMatch:
return log.object_repr return log.object_repr
return format_html('<a href="{}">{}</a>', url, log.object_repr) return format_html('<a href="{}">{}</a>', url, log.object_repr)
content_object_link.short_description = _("Content object")
content_object_link.admin_order_field = 'object_repr'
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):
""" Add rel_opts and object to context """ """ Add rel_opts and object to context """

View File

@ -0,0 +1,22 @@
BBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB
BBBB BBBB BBBBBBBBBB
BBBBB BBBBBBBBBBBBBBBBBB
BBBBBBBB
BBBBB BBBBBBBBBBB
BB BBBBBBBB
XXXX XXXXXXXXXXXXXXXXXXXX
XX XXXXXXBBB BBBBBBBBBBBBBXX gettext(u'Home') XXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFFFFFFFFFFFXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXX gettext(u'History') XXXX
XXXXXXXX gettext(u'Edit')
XXXXXX
BBBB
BBBBB
BBBBBBBB

View File

@ -0,0 +1,43 @@
BBBBBBB BBBBBBBBBBBBBBBBBBBBBB
BBBB BBBB BBBBBBBBBB BBBBBB
BBBBB BBBBBBBBBBB
XXXX XXXXXXXXXXXXXXXXXXXX
XX XXXXXXBBB BBBBBBBBBBBBBXX gettext(u'Home') XXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXX
XXXXXXXX XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBXXFFFFFFFFFFFFFFFFFFXXXX
XXXXXXXX gettext(u'History')
XXXXXX
BBBBBBBB
BBBBB BBBBBBB
XXXX XXXXXXXXXXXXXXXXXX
XXXX XXXXXXXXXXXXXXX
BB BBBBBBBBBBB
XXXXXX XXXXXXXXXXXXXXXXXXXX
XXXXXXX
XXXX
XXX XXXXXXXXXXXX gettext(u'Date/time') XXXXX
XXX XXXXXXXXXXXX gettext(u'User') XXXXX
XXX XXXXXXXXXXXX gettext(u'Action') XXXXX
XXXXX
XXXXXXXX
XXXXXXX
BBB BBBBBB BB BBBBBBBBBBB
XXXX
XXX XXXXXXXXXXXXFFFFFFFFFFFFFFFFFFFFFFXXXXX
XXXXBB BBBBBBBBBBBBBBBBBBBBBBBBB XXBBBBBXXXXX
XXXXBB BBBBBBBBBBBBBBBBBB BBB BBB BBBBBBBBBBBBBBBBBBBBB gettext(u'Added') BBBBBBBBB XX XXXXXXBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXX XXXXXBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXXXXXXXXXXXXX
XXXXX
BBBBBB
XXXXXXXX
XXXXXXXX
BBBB
XXX gettext(u"This object doesn't have a change history. It probably wasn't added via this admin site.") XXXX
BBBBB
XXXXXX
XXXXXX
BBBBBBBB

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.issues.apps.IssuesConfig'

View File

@ -51,9 +51,6 @@ class MessageReadOnlyInline(admin.TabularInline):
'all': ('orchestra/css/hide-inline-id.css',) 'all': ('orchestra/css/hide-inline-id.css',)
} }
@admin.display(
description=_("Content")
)
@mark_safe @mark_safe
def content_html(self, msg): def content_html(self, msg):
context = { context = {
@ -69,6 +66,7 @@ class MessageReadOnlyInline(admin.TabularInline):
content = '<div style="padding-left:20px;">%s</div>' % content content = '<div style="padding-left:20px;">%s</div>' % content
return header + content return header + content
content_html.short_description = _("Content")
def has_add_permission(self, request, obj): def has_add_permission(self, request, obj):
return False return False
@ -116,15 +114,12 @@ class TicketInline(admin.TabularInline):
colored_state = admin_colored('state', colors=STATE_COLORS, bold=False) colored_state = admin_colored('state', colors=STATE_COLORS, bold=False)
colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False) colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
@admin.display(
description='#'
)
@mark_safe @mark_safe
def ticket_id(self, instance): def ticket_id(self, instance):
return '<b>%s</b>' % admin_link()(instance) return '<b>%s</b>' % admin_link()(instance)
ticket_id.short_description = '#'
@admin.register(Ticket)
class TicketAdmin(ExtendedModelAdmin): class TicketAdmin(ExtendedModelAdmin):
list_display = ( list_display = (
'unbold_id', 'bold_subject', 'display_creator', 'display_owner', 'unbold_id', 'bold_subject', 'display_creator', 'display_owner',
@ -200,9 +195,6 @@ class TicketAdmin(ExtendedModelAdmin):
display_state = admin_colored('state', colors=STATE_COLORS, bold=False) display_state = admin_colored('state', colors=STATE_COLORS, bold=False)
display_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False) display_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
@admin.display(
description='Summary'
)
@mark_safe @mark_safe
def display_summary(self, ticket): def display_summary(self, ticket):
context = { context = {
@ -218,26 +210,23 @@ class TicketAdmin(ExtendedModelAdmin):
}) })
context['updated'] = '. Updated by %(updater)s about %(updated)s' % context context['updated'] = '. Updated by %(updater)s about %(updated)s' % context
return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context
display_summary.short_description = 'Summary'
@admin.display(
description="#",
ordering='id',
)
def unbold_id(self, ticket): def unbold_id(self, ticket):
""" Unbold id if ticket is read """ """ Unbold id if ticket is read """
if ticket.is_read_by(self.user): if ticket.is_read_by(self.user):
return format_html('<span style="font-weight:normal;font-size:11px;">{}</span>', ticket.pk) return format_html('<span style="font-weight:normal;font-size:11px;">{}</span>', ticket.pk)
return ticket.pk return ticket.pk
unbold_id.short_description = "#"
unbold_id.admin_order_field = 'id'
@admin.display(
description=_("Subject"),
ordering='subject',
)
def bold_subject(self, ticket): def bold_subject(self, ticket):
""" Bold subject when tickets are unread for request.user """ """ Bold subject when tickets are unread for request.user """
if ticket.is_read_by(self.user): if ticket.is_read_by(self.user):
return ticket.subject return ticket.subject
return format_html("<strong class='unread'>{}</strong>", ticket.subject) return format_html("<strong class='unread'>{}</strong>", ticket.subject)
bold_subject.short_description = _("Subject")
bold_subject.admin_order_field = 'subject'
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """
@ -294,7 +283,6 @@ class TicketAdmin(ExtendedModelAdmin):
return HttpResponse(data_formated) return HttpResponse(data_formated)
@admin.register(Queue)
class QueueAdmin(admin.ModelAdmin): class QueueAdmin(admin.ModelAdmin):
list_display = ('name', 'default', 'num_tickets') list_display = ('name', 'default', 'num_tickets')
actions = (set_default_queue,) actions = (set_default_queue,)
@ -306,15 +294,13 @@ class QueueAdmin(admin.ModelAdmin):
'all': ('orchestra/css/hide-inline-id.css',) 'all': ('orchestra/css/hide-inline-id.css',)
} }
@admin.display(
description=_("Tickets"),
ordering='tickets__count',
)
def num_tickets(self, queue): def num_tickets(self, queue):
num = queue.tickets__count num = queue.tickets__count
url = reverse('admin:issues_ticket_changelist') url = reverse('admin:issues_ticket_changelist')
url += '?queue=%i' % queue.pk url += '?queue=%i' % queue.pk
return format_html('<a href="{}">{}</a>', url, num) return format_html('<a href="{}">{}</a>', url, num)
num_tickets.short_description = _("Tickets")
num_tickets.admin_order_field = 'tickets__count'
def get_list_display(self, request): def get_list_display(self, request):
""" show notifications """ """ show notifications """
@ -333,3 +319,5 @@ class QueueAdmin(admin.ModelAdmin):
return qs return qs
admin.site.register(Ticket, TicketAdmin)
admin.site.register(Queue, QueueAdmin)

View File

@ -1,10 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:27
from __future__ import unicode_literals from __future__ import unicode_literals
import django.db.models.deletion import datetime
from django.db import models, migrations
import orchestra.models.fields
from django.conf import settings from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.utils.timezone import utc
import orchestra.models.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -17,7 +20,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Message', name='Message',
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('author_name', models.CharField(blank=True, max_length=256, verbose_name='author name')), ('author_name', models.CharField(blank=True, max_length=256, verbose_name='author name')),
('content', models.TextField(verbose_name='content')), ('content', models.TextField(verbose_name='content')),
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='created on')), ('created_on', models.DateTimeField(auto_now_add=True, verbose_name='created on')),
@ -30,28 +33,28 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Queue', name='Queue',
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, verbose_name='name', unique=True)), ('name', models.CharField(max_length=128, unique=True, verbose_name='name')),
('verbose_name', models.CharField(blank=True, max_length=128, verbose_name='verbose_name')), ('verbose_name', models.CharField(blank=True, max_length=128, verbose_name='verbose_name')),
('default', models.BooleanField(verbose_name='default', default=False)), ('default', models.BooleanField(default=False, verbose_name='default')),
('notify', orchestra.models.fields.MultiSelectField(blank=True, max_length=256, help_text='Contacts to notify by email', verbose_name='notify', default=('SUPPORT', 'ADMIN', 'BILLING', 'TECH', 'ADDS', 'EMERGENCY'), choices=[('SUPPORT', 'Support tickets'), ('ADMIN', 'Administrative'), ('BILLING', 'Billing'), ('TECH', 'Technical'), ('ADDS', 'Announcements'), ('EMERGENCY', 'Emergency contact')])), ('notify', orchestra.models.fields.MultiSelectField(blank=True, choices=[('SUPPORT', 'Support tickets'), ('ADMIN', 'Administrative'), ('BILLING', 'Billing'), ('TECH', 'Technical'), ('ADDS', 'Announcements'), ('EMERGENCY', 'Emergency contact')], default=('SUPPORT', 'ADMIN', 'BILLING', 'TECH', 'ADDS', 'EMERGENCY'), help_text='Contacts to notify by email', max_length=256, verbose_name='notify')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Ticket', name='Ticket',
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('creator_name', models.CharField(blank=True, max_length=256, verbose_name='creator name')), ('creator_name', models.CharField(blank=True, max_length=256, verbose_name='creator name')),
('subject', models.CharField(max_length=256, verbose_name='subject')), ('subject', models.CharField(max_length=256, verbose_name='subject')),
('description', models.TextField(verbose_name='description')), ('description', models.TextField(verbose_name='description')),
('priority', models.CharField(max_length=32, default='MEDIUM', verbose_name='priority', choices=[('HIGH', 'High'), ('MEDIUM', 'Medium'), ('LOW', 'Low')])), ('priority', models.CharField(choices=[('HIGH', 'High'), ('MEDIUM', 'Medium'), ('LOW', 'Low')], default='MEDIUM', max_length=32, verbose_name='priority')),
('state', models.CharField(max_length=32, default='NEW', verbose_name='state', choices=[('NEW', 'New'), ('IN_PROGRESS', 'In Progress'), ('RESOLVED', 'Resolved'), ('FEEDBACK', 'Feedback'), ('REJECTED', 'Rejected'), ('CLOSED', 'Closed')])), ('state', models.CharField(choices=[('NEW', 'New'), ('IN_PROGRESS', 'In Progress'), ('RESOLVED', 'Resolved'), ('FEEDBACK', 'Feedback'), ('REJECTED', 'Rejected'), ('CLOSED', 'Closed')], default='NEW', max_length=32, verbose_name='state')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='modified')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='modified')),
('cc', models.TextField(blank=True, help_text='emails to send a carbon copy to', verbose_name='CC')), ('cc', models.TextField(blank=True, help_text='emails to send a carbon copy to', verbose_name='CC')),
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tickets_created', null=True, to=settings.AUTH_USER_MODEL, verbose_name='created by')), ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tickets_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, blank=True, related_name='tickets_owned', null=True, to=settings.AUTH_USER_MODEL, verbose_name='assigned to')), ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tickets_owned', to=settings.AUTH_USER_MODEL, verbose_name='assigned to')),
('queue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, blank=True, related_name='tickets', null=True, to='issues.Queue')), ('queue', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to='issues.Queue')),
], ],
options={ options={
'ordering': ['-updated_at'], 'ordering': ['-updated_at'],
@ -60,7 +63,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='TicketTracker', name='TicketTracker',
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trackers', to='issues.Ticket', verbose_name='ticket')), ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trackers', to='issues.Ticket', verbose_name='ticket')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_trackers', to=settings.AUTH_USER_MODEL, verbose_name='user')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_trackers', to=settings.AUTH_USER_MODEL, verbose_name='user')),
], ],
@ -74,4 +77,34 @@ class Migration(migrations.Migration):
name='tickettracker', name='tickettracker',
unique_together=set([('ticket', 'user')]), unique_together=set([('ticket', 'user')]),
), ),
migrations.AlterField(
model_name='ticket',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='created'),
),
migrations.RemoveField(
model_name='message',
name='created_on',
),
migrations.AddField(
model_name='message',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 3, 20, 10, 27, 45, 766388, tzinfo=utc), verbose_name='created at'),
preserve_default=False,
),
migrations.AlterField(
model_name='ticket',
name='creator',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_created', to=settings.AUTH_USER_MODEL, verbose_name='created by'),
),
migrations.AlterField(
model_name='ticket',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_owned', to=settings.AUTH_USER_MODEL, verbose_name='assigned to'),
),
migrations.AlterField(
model_name='ticket',
name='queue',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='issues.Queue'),
),
] ]

View File

@ -1,114 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:27
from __future__ import unicode_literals
import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.utils.timezone import utc
import orchestra.models.fields
class Migration(migrations.Migration):
replaces = [('issues', '0001_initial'), ('issues', '0002_auto_20150709_1018'), ('issues', '0003_auto_20160320_1127'), ('issues', '0004_auto_20170528_2011')]
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('author_name', models.CharField(blank=True, max_length=256, verbose_name='author name')),
('content', models.TextField(verbose_name='content')),
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='created on')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_messages', to=settings.AUTH_USER_MODEL, verbose_name='author')),
],
options={
'get_latest_by': 'id',
},
),
migrations.CreateModel(
name='Queue',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True, verbose_name='name')),
('verbose_name', models.CharField(blank=True, max_length=128, verbose_name='verbose_name')),
('default', models.BooleanField(default=False, verbose_name='default')),
('notify', orchestra.models.fields.MultiSelectField(blank=True, choices=[('SUPPORT', 'Support tickets'), ('ADMIN', 'Administrative'), ('BILLING', 'Billing'), ('TECH', 'Technical'), ('ADDS', 'Announcements'), ('EMERGENCY', 'Emergency contact')], default=('SUPPORT', 'ADMIN', 'BILLING', 'TECH', 'ADDS', 'EMERGENCY'), help_text='Contacts to notify by email', max_length=256, verbose_name='notify')),
],
),
migrations.CreateModel(
name='Ticket',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('creator_name', models.CharField(blank=True, max_length=256, verbose_name='creator name')),
('subject', models.CharField(max_length=256, verbose_name='subject')),
('description', models.TextField(verbose_name='description')),
('priority', models.CharField(choices=[('HIGH', 'High'), ('MEDIUM', 'Medium'), ('LOW', 'Low')], default='MEDIUM', max_length=32, verbose_name='priority')),
('state', models.CharField(choices=[('NEW', 'New'), ('IN_PROGRESS', 'In Progress'), ('RESOLVED', 'Resolved'), ('FEEDBACK', 'Feedback'), ('REJECTED', 'Rejected'), ('CLOSED', 'Closed')], default='NEW', max_length=32, verbose_name='state')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='modified')),
('cc', models.TextField(blank=True, help_text='emails to send a carbon copy to', verbose_name='CC')),
('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tickets_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tickets_owned', to=settings.AUTH_USER_MODEL, verbose_name='assigned to')),
('queue', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to='issues.Queue')),
],
options={
'ordering': ['-updated_at'],
},
),
migrations.CreateModel(
name='TicketTracker',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trackers', to='issues.Ticket', verbose_name='ticket')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_trackers', to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
),
migrations.AddField(
model_name='message',
name='ticket',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='issues.Ticket', verbose_name='ticket'),
),
migrations.AlterUniqueTogether(
name='tickettracker',
unique_together=set([('ticket', 'user')]),
),
migrations.AlterField(
model_name='ticket',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='created'),
),
migrations.RemoveField(
model_name='message',
name='created_on',
),
migrations.AddField(
model_name='message',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 3, 20, 10, 27, 45, 766388, tzinfo=utc), verbose_name='created at'),
preserve_default=False,
),
migrations.AlterField(
model_name='ticket',
name='creator',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_created', to=settings.AUTH_USER_MODEL, verbose_name='created by'),
),
migrations.AlterField(
model_name='ticket',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_owned', to=settings.AUTH_USER_MODEL, verbose_name='assigned to'),
),
migrations.AlterField(
model_name='ticket',
name='queue',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='issues.Queue'),
),
]

View File

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('issues', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='created_at',
field=models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='created'),
),
]

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import datetime
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('issues', '0002_auto_20150709_1018'),
]
operations = [
migrations.RemoveField(
model_name='message',
name='created_on',
),
migrations.AddField(
model_name='message',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 3, 20, 10, 27, 45, 766388, tzinfo=utc), verbose_name='created at'),
preserve_default=False,
),
]

View File

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-05-28 18:11
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('issues', '0003_auto_20160320_1127'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='creator',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_created', to=settings.AUTH_USER_MODEL, verbose_name='created by'),
),
migrations.AlterField(
model_name='ticket',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_owned', to=settings.AUTH_USER_MODEL, verbose_name='assigned to'),
),
migrations.AlterField(
model_name='ticket',
name='queue',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='issues.Queue'),
),
]

View File

@ -0,0 +1,55 @@
XXXXXXXXX XXXX XXXXXX XXXXXXXXXXXX XXXXX XXX XXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXX
XXXXX XXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXX XX
XXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXX XX
XXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXX
XXXXXX XXXXXXXXXXXXXXXX
XX X XXXXXXXXXXXX XXXXXXXX XXXXXXXXXXX XXXXXXXXXX XXXXX XXXXXXXXXXX XXXXXXX XXXXXX XXXXX X
XXXX X XXXXXXXXXXXX XXXXXXXX XXXXXXXXXXX XXXXXXXXXX XXXXX XXXXXX XXXXX X
XXXXX XX X XXXXXXXXXXXX XXXX X
XXXXX XX X XXXXXXXXXXXXXXX XXXX XXXXXXXXXXXXXXXXX XXXXXXXX XXXXXXX XXXX XXXXXXXXXXXXXXX XXXXXXXX
XXXXX XX XXXX X XXXXXXXXXX XXXXXX X
XXXXX XX XX X XXXXXXXXXX XXXXXX XXXXXXXXXXX XXXXX X
XXXXX XX XX X XXXXXXXXXX XXXXXX XXXXXXXXXXX XXXXX X
XXXXX XX XX X XXXXXXXXXX XXXXXX XXXXXXXXXXX XXXXX X
XXXXX XX XXXX X XXXXXXXXXXXXXXXXX XXXX XXXXXXXXXXXXXXX XXXX X
XXXXXXXX
XXXXXXX
XXXXXX
XXXXXXXXXXXX XXXXXX XXXXX XXXXXXXXXXXXXX
XXXXXX XXXXXXXXXXXXX
XXXXXXX XXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X XX XXXX XXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXX
XXXXXXX XXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXX X XXXX XXXX X XX XXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXX XXXXXXXX XXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXX XX XXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXX XXXXXXXXXX XXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX XXXX XXXXXXXX XXXXXXXXXXXXXXXXXXXXX
XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXX XXXX XXXX XXX XXXX XXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXX XXXX XXXX XXXX XXXX XXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXX
XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXX XXXXX X XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXX XXXXX XXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXX
XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXX
XXXXXXXX
XXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXX XXXXXXXXX XX XXXXXXXX XXXXXXXXXXX
XXXX
XXXXXXX
XXXXXXX

View File

@ -10,9 +10,6 @@ from .helpers import is_valid_domain, read_live_lineages, configure_cert
from .forms import LetsEncryptForm from .forms import LetsEncryptForm
@admin.action(
description="Let's encrypt!"
)
def letsencrypt(modeladmin, request, queryset): def letsencrypt(modeladmin, request, queryset):
wildcards = set() wildcards = set()
domains = set() domains = set()
@ -115,3 +112,4 @@ def letsencrypt(modeladmin, request, queryset):
'form': form, 'form': form,
} }
return TemplateResponse(request, 'admin/orchestra/generic_confirmation.html', context) return TemplateResponse(request, 'admin/orchestra/generic_confirmation.html', context)
letsencrypt.short_description = "Let's encrypt!"

View File

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.lists.apps.ListsConfig'

View File

@ -16,7 +16,6 @@ from .filters import HasCustomAddressListFilter
from .models import List from .models import List
@admin.register(List)
class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ( list_display = (
'name', 'address_name', 'address_domain_link', 'account_link', 'display_active' 'name', 'address_name', 'address_domain_link', 'account_link', 'display_active'
@ -57,3 +56,4 @@ class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
address_domain_link = admin_link('address_domain', order='address_domain__name') address_domain_link = admin_link('address_domain', order='address_domain__name')
admin.site.register(List, ListAdmin)

View File

@ -57,6 +57,14 @@ class MailmanVirtualDomainController(ServiceController):
def commit(self): def commit(self):
context = self.get_context_files() context = self.get_context_files()
super(MailmanVirtualDomainController, self).commit() super(MailmanVirtualDomainController, self).commit()
self.append(textwrap.dedent("""
# Apply changes if needed
if [[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]]; then
postmap %(virtual_alias_domains)s
systemctl reload postfix
fi
exit $exit_code""") % context
)
def get_context_files(self): def get_context_files(self):
return { return {
@ -101,7 +109,7 @@ class MailmanController(MailmanVirtualDomainController):
for suffix in self.address_suffixes: for suffix in self.address_suffixes:
context['suffix'] = suffix context['suffix'] = suffix
# Because mailman doesn't properly handle lists aliases we need virtual aliases # Because mailman doesn't properly handle lists aliases we need virtual aliases
if context['address_name'] != context['name']: if context['address_name'] != context['name'] or context['address_domain'] != settings.LISTS_DEFAULT_DOMAIN:
aliases.append("%(address_name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s@grups.pangea.org" % context) aliases.append("%(address_name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s@grups.pangea.org" % context)
return '\n'.join(aliases) return '\n'.join(aliases)
@ -165,7 +173,18 @@ class MailmanController(MailmanVirtualDomainController):
def commit(self): def commit(self):
pass context = self.get_context_files()
self.append(textwrap.dedent("""
# Apply changes if needed
if [[ $UPDATED_VIRTUAL_ALIAS == 1 ]]; then
postmap %(virtual_alias)s
fi
if [[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]]; then
systemctl reload postfix
fi
exit $exit_code""") % context
)
def get_context_files(self): def get_context_files(self):
return { return {
@ -199,7 +218,7 @@ class MailmanTraffic(ServiceMonitor):
model = 'lists.List' model = 'lists.List'
resource = ServiceMonitor.TRAFFIC resource = ServiceMonitor.TRAFFIC
verbose_name = _("Mailman traffic") verbose_name = _("Mailman traffic")
script_executable = '/usr/bin/python' script_executable = '/usr/bin/python3'
monthly_sum_old_values = True monthly_sum_old_values = True
doc_settings = (settings, doc_settings = (settings,
('LISTS_MAILMAN_POST_LOG_PATH',) ('LISTS_MAILMAN_POST_LOG_PATH',)
@ -210,9 +229,10 @@ class MailmanTraffic(ServiceMonitor):
context = { context = {
'postlogs': str((postlog, postlog+'.1')), 'postlogs': str((postlog, postlog+'.1')),
'current_date': self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z"), 'current_date': self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z"),
'default_domain': settings.LISTS_DEFAULT_DOMAIN,
} }
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
import re
import subprocess import subprocess
import sys import sys
from datetime import datetime from datetime import datetime
@ -243,7 +263,6 @@ class MailmanTraffic(ServiceMonitor):
'Nov': '11', 'Nov': '11',
'Dec': '12', 'Dec': '12',
}} }}
mailman_addr = re.compile(r'.*-(admin|bounces|confirm|join|leave|owner|request|subscribe|unsubscribe)@.*|mailman@.*')
def prepare(object_id, list_name, ini_date): def prepare(object_id, list_name, ini_date):
global lists global lists
@ -255,39 +274,33 @@ class MailmanTraffic(ServiceMonitor):
for postlog in postlogs: for postlog in postlogs:
try: try:
with open(postlog, 'r') as postlog: with open(postlog, 'r') as postlog:
recps_dict = {{}}
for line in postlog.readlines(): for line in postlog.readlines():
line = line.split() line = line.split()
if len(line) < 11: if 'recips,' in line:
__, __, __, __, __, id, __, __, list_name, __, recps = line[:11]
recps_dict[id] = recps
continue continue
month, day, time, year, __, __, __, list_name, __, addr, size = line[:11] if not 'bytes' in line:
continue
month, day, time, year, __, __, __, __, list_name, __, addr, size = line[:12]
try: try:
list_name = list_name.split('@')[0]
list = lists[list_name] list = lists[list_name]
except KeyError: except KeyError:
continue continue
else: else:
# discard mailman messages because of inconsistent POST logging
if mailman_addr.match(addr):
continue
date = year + months[month] + day + time.replace(':', '') date = year + months[month] + day + time.replace(':', '')
if list[0] < int(date) < end_date: if list[0] < int(date) < end_date:
size = size[5:-1] if id in recps_dict:
try: list[2] += int(size) * int(recps_dict[id])
list[2] += int(size)
except ValueError:
# anonymized post
pass
except IOError as e: except IOError as e:
sys.stderr.write(str(e)+'\\n') sys.stderr.write(str(e)+'\\n')
for list_name, opts in lists.items(): for list_name, opts in lists.items():
__, object_id, size = opts __, object_id, size = opts
if size: print(object_id, size)
cmd = ' '.join(('list_members', list_name, '| wc -l'))
ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subscribers = ps.communicate()[0].strip()
size *= int(subscribers)
sys.stderr.write("%s %s*%s traffic*subscribers\\n" % (object_id, size, subscribers))
print object_id, size
""").format(**context) """).format(**context)
) )

View File

@ -0,0 +1,117 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-06 10:23+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin.py:28 admin.py:42
msgid "Address"
msgstr "Direccio"
#: admin.py:32
msgid "Admin"
msgstr "Administrador"
#: admin.py:44
#, python-format
msgid "Additional address besides the default &lt;name&gt;@%s"
msgstr "Adreça addicional a més del valor predeterminat &lt;name&gt;@%s"
#: backends.py:16
msgid "Mailman virtdomain-only"
msgstr ""
#: backends.py:201
msgid "Mailman traffic"
msgstr "Trànsit de Mailman"
#: backends.py:309
msgid "Mailman subscribers"
msgstr "Subscriptors Mailman"
#: filters.py:7
msgid "has custom address"
msgstr "té adreça personalitzada"
#: filters.py:12
msgid "True"
msgstr ""
#: filters.py:13
msgid "False"
msgstr ""
#: models.py:13
msgid "name"
msgstr "nom"
#: models.py:14
#, python-format
msgid "Default list address &lt;name&gt;@%s"
msgstr "Adreça de llista predeterminada &lt;name&gt;@%s"
#: models.py:15
msgid "address name"
msgstr "Nom de l'adreça"
#: models.py:18
msgid "address domain"
msgstr "Domini adicional"
#: models.py:19
msgid "admin email"
msgstr "Correu electrònic d'administració"
#: models.py:20
msgid "Administration email address"
msgstr "Adreça de correu electrònic d'administració"
#: models.py:21
msgid "Account"
msgstr "Compte"
#: models.py:24
msgid "active"
msgstr "actiu"
#: models.py:25
msgid ""
"Designates whether this account should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
"Designa si aquest compte ha de ser tractat com a actiu. Deselecteu això "
"En lloc de suprimir els comptes."
#: models.py:47
msgid "Domain should be selected for provided address name."
msgstr "El domini s'ha de seleccionar per al nom de l'adreça proporcionat."
#: serializers.py:20
msgid "Password"
msgstr "Contrasenya"
#: serializers.py:25
msgid ""
"Enter a valid password. This value may contain any ascii character except "
"for '/\"/\\/ characters."
msgstr ""
"Introduïu una contrasenya vàlida. Aquest valor pot contenir qualsevol personatge ASCII excepte"
"Per a '/\"/\\/caràcters. "
#: serializers.py:43
msgid "address_domains should should be provided when providing an addres_name"
msgstr "S'hauria de proporcionar address_domains en proporcionar un address_name"

View File

@ -0,0 +1,117 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-06 10:03+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin.py:28 admin.py:42
msgid "Address"
msgstr "Dirección"
#: admin.py:32
msgid "Admin"
msgstr "Administrador"
#: admin.py:44
#, python-format
msgid "Additional address besides the default &lt;name&gt;@%s"
msgstr "Dirección adicional además del valor predeterminado &lt;name&gt;@%s"
#: backends.py:16
msgid "Mailman virtdomain-only"
msgstr ""
#: backends.py:201
msgid "Mailman traffic"
msgstr "Tráfico Mailman"
#: backends.py:309
msgid "Mailman subscribers"
msgstr "Subscriptores Mailman"
#: filters.py:7
msgid "has custom address"
msgstr "tiene dirección personalizada"
#: filters.py:12
msgid "True"
msgstr ""
#: filters.py:13
msgid "False"
msgstr ""
#: models.py:13
msgid "name"
msgstr "nombre"
#: models.py:14
#, python-format
msgid "Default list address &lt;name&gt;@%s"
msgstr "dirección de lista por defecto &lt;name&gt;@%s"
#: models.py:15
msgid "address name"
msgstr "nombre de dirección"
#: models.py:18
msgid "address domain"
msgstr "dominio adicional"
#: models.py:19
msgid "admin email"
msgstr "correo administrador"
#: models.py:20
msgid "Administration email address"
msgstr "dirección correo administrador"
#: models.py:21
msgid "Account"
msgstr "Cuenta"
#: models.py:24
msgid "active"
msgstr "activo"
#: models.py:25
msgid ""
"Designates whether this account should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
"Designa si esta cuenta debe tratarse como activa. Deseleccionar esto "
"En lugar de eliminar cuentas."
#: models.py:47
msgid "Domain should be selected for provided address name."
msgstr "El dominio debe seleccionarse para el nombre de la dirección proporcionado."
#: serializers.py:20
msgid "Password"
msgstr "Contraseña"
#: serializers.py:25
msgid ""
"Enter a valid password. This value may contain any ascii character except "
"for '/\"/\\/ characters."
msgstr ""
"Ingrese una contraseña válida. Este valor puede contener cualquier carácter ASCII excepto"
"Para '/\"/\\/caracteres."
#: serializers.py:43
msgid "address_domains should should be provided when providing an addres_name"
msgstr "address_domains debe proporcionarse al proporcionar un addres_name"

View File

@ -1,4 +1,6 @@
# Generated by Django 2.2.28 on 2023-09-01 14:59 # -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:27
from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -8,10 +10,8 @@ import orchestra.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True
dependencies = [ dependencies = [
('domains', '__first__'), ('domains', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
@ -20,15 +20,46 @@ class Migration(migrations.Migration):
name='List', name='List',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Default list address &lt;name&gt;@grups.pangea.org', max_length=64, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name')), ('name', models.CharField(help_text='Default list address &lt;name&gt;@lists.orchestra.lan', max_length=128, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
('address_name', models.CharField(blank=True, max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='address name')), ('address_name', models.CharField(blank=True, max_length=128, validators=[orchestra.core.validators.validate_name], verbose_name='address name')),
('admin_email', models.EmailField(help_text='Administration email address', max_length=254, verbose_name='admin email')), ('admin_email', models.EmailField(help_text='Administration email address', max_length=254, verbose_name='admin email')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL, verbose_name='Account')), ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
('address_domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='domains.Domain', verbose_name='address domain')), ('address_domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='domains.Domain', verbose_name='address domain')),
], ],
options={ ),
'unique_together': {('address_name', 'address_domain')}, migrations.AlterUniqueTogether(
}, name='list',
unique_together=set([('address_name', 'address_domain')]),
),
migrations.AlterField(
model_name='list',
name='address_domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='domains.Domain', verbose_name='address domain'),
),
migrations.AlterField(
model_name='list',
name='address_name',
field=models.CharField(blank=True, max_length=52, validators=[orchestra.core.validators.validate_name], verbose_name='address name'),
),
migrations.AlterField(
model_name='list',
name='name',
field=models.CharField(help_text='Default list address &lt;name&gt;@grups.pangea.org', max_length=52, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name'),
),
migrations.AlterField(
model_name='list',
name='address_name',
field=models.CharField(blank=True, max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='address name'),
),
migrations.AlterField(
model_name='list',
name='name',
field=models.CharField(help_text='Default list address &lt;name&gt;@grups.pangea.org', max_length=64, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name'),
),
migrations.AlterField(
model_name='list',
name='name',
field=models.CharField(help_text='Default list address &lt;name&gt;@lists.orchestra.lan', max_length=64, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name'),
), ),
] ]

View File

@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2021-04-22 11:27
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import orchestra.core.validators
class Migration(migrations.Migration):
replaces = [('lists', '0001_initial'), ('lists', '0002_auto_20160912_1221'), ('lists', '0003_auto_20160912_1241'), ('lists', '0004_auto_20210330_1049')]
initial = True
dependencies = [
('domains', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='List',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Default list address &lt;name&gt;@lists.orchestra.lan', max_length=128, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
('address_name', models.CharField(blank=True, max_length=128, validators=[orchestra.core.validators.validate_name], verbose_name='address name')),
('admin_email', models.EmailField(help_text='Administration email address', max_length=254, verbose_name='admin email')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
('address_domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='domains.Domain', verbose_name='address domain')),
],
),
migrations.AlterUniqueTogether(
name='list',
unique_together=set([('address_name', 'address_domain')]),
),
migrations.AlterField(
model_name='list',
name='address_domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='domains.Domain', verbose_name='address domain'),
),
migrations.AlterField(
model_name='list',
name='address_name',
field=models.CharField(blank=True, max_length=52, validators=[orchestra.core.validators.validate_name], verbose_name='address name'),
),
migrations.AlterField(
model_name='list',
name='name',
field=models.CharField(help_text='Default list address &lt;name&gt;@grups.pangea.org', max_length=52, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name'),
),
migrations.AlterField(
model_name='list',
name='address_name',
field=models.CharField(blank=True, max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='address name'),
),
migrations.AlterField(
model_name='list',
name='name',
field=models.CharField(help_text='Default list address &lt;name&gt;@grups.pangea.org', max_length=64, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name'),
),
migrations.AlterField(
model_name='list',
name='name',
field=models.CharField(help_text='Default list address &lt;name&gt;@lists.orchestra.lan', max_length=64, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name'),
),
]

Some files were not shown because too many files have changed in this diff Show More