<commit_before>from django.conf.urls import patterns, url
from django.contrib import admin, messages
from django.contrib.admin import SimpleListFilter
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.core.exceptions import ValidationError
from django.db import models as django_models
from django.forms import ModelForm, ModelMultipleChoiceField, ChoiceField, RadioSelect
from django.http import HttpResponseRedirect
from django.utils import six
from django.utils.translation import ungettext
from django.shortcuts import render

from nodeconductor.core.admin import get_admin_url, ExecutorAdminAction
from nodeconductor.core.models import User
from nodeconductor.core.tasks import send_task
from nodeconductor.core import utils
from nodeconductor.quotas.admin import QuotaInline
from nodeconductor.structure import models, SupportedServices, executors

class FormRequestAdminMixin(object):
    """
    This mixin allows you to get current request user in the model admin form,
    which then passed to add_user method, so that user which granted role,
    is stored in the permission model.
    """
    def get_form(self, request, obj=None, **kwargs):
        form = super(FormRequestAdminMixin, self).get_form(request, obj=obj, **kwargs)
        form.request = request
        return form

class ChangeReadonlyMixin(object):

    add_readonly_fields = ()
    change_readonly_fields = ()

    def get_readonly_fields(self, request, obj=None):
        fields = super(ChangeReadonlyMixin, self).get_readonly_fields(request, obj)
        if hasattr(request, '_is_admin_add_view') and request._is_admin_add_view:
            return tuple(set(fields) | set(self.add_readonly_fields))
        else:
            return tuple(set(fields) | set(self.change_readonly_fields))

    def add_view(self, request, *args, **kwargs):
        request._is_admin_add_view = True
        return super(ChangeReadonlyMixin, self).add_view(request, *args, **kwargs)

class ProtectedModelMixin(object):
    def delete_view(self, request, *args, **kwargs):
        try:
            response = super(ProtectedModelMixin, self).delete_view(request, *args, **kwargs)
        except django_models.ProtectedError as e:
            self.message_user(request, e, messages.ERROR)
            return HttpResponseRedirect('.')
        else:
            return response

class ResourceCounterFormMixin(object):

    def get_vm_count(self, obj):
        return obj.quotas.get(name=obj.Quotas.nc_vm_count).usage

    get_vm_count.short_description = 'VM count'

    def get_app_count(self, obj):
        return obj.quotas.get(name=obj.Quotas.nc_app_count).usage

    get_app_count.short_description = 'Application count'

    def get_private_cloud_count(self, obj):
        return obj.quotas.get(name=obj.Quotas.nc_private_cloud_count).usage

    get_private_cloud_count.short_description = 'Private cloud count'

class CustomerAdminForm(ModelForm):
    owners = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                      widget=FilteredSelectMultiple(verbose_name='Owners', is_stacked=False))
    support_users = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                             widget=FilteredSelectMultiple(verbose_name='Support users',
                                                                           is_stacked=False))

    def __init__(self, *args, **kwargs):
        super(CustomerAdminForm, self).__init__(*args, **kwargs)
        if self.instance and self.instance.pk:
            self.owners = self.instance.get_owners()
            self.support_users = self.instance.get_support_users()
            self.fields['owners'].initial = self.owners
            self.fields['support_users'].initial = self.support_users
        else:
            self.owners = User.objects.none()
            self.support_users = User.objects.none()

    def save(self, commit=True):
        customer = super(CustomerAdminForm, self).save(commit=False)

        if not customer.pk:
            customer.save()

        self.populate_users('owners', customer, models.CustomerRole.OWNER)
        self.populate_users('support_users', customer, models.CustomerRole.SUPPORT)

        return customer

    def populate_users(self, field_name, customer, role):
        field = getattr(self, field_name)
        new_users = self.cleaned_data[field_name]

        removed_users = field.exclude(pk__in=new_users)
        for user in removed_users:
            customer.remove_user(user, role)

        added_users = new_users.exclude(pk__in=field)
        for user in added_users:
            # User role within customer must be unique.
            if not customer.has_user(user):
                customer.add_user(user, role, self.request.user)

        self.save_m2m()

class CustomerAdmin(FormRequestAdminMixin,
                    ResourceCounterFormMixin,
                    ProtectedModelMixin,
                    admin.ModelAdmin):
    form = CustomerAdminForm
    fields = ('name', 'image', 'native_name', 'abbreviation', 'contact_details', 'registration_code',
              'country', 'vat_code', 'is_company', 'balance', 'owners', 'support_users')
    readonly_fields = ['balance']
    list_display = ['name', 'uuid', 'abbreviation', 'created', 'get_vm_count', 'get_app_count',
                    'get_private_cloud_count']
    inlines = [QuotaInline]

class ProjectAdminForm(ModelForm):
    admins = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                      widget=FilteredSelectMultiple(verbose_name='Admins', is_stacked=False))
    managers = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                        widget=FilteredSelectMultiple(verbose_name='Managers', is_stacked=False))
    support_users = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                             widget=FilteredSelectMultiple(verbose_name='Support users',
                                                                           is_stacked=False))

    def __init__(self, *args, **kwargs):
        super(ProjectAdminForm, self).__init__(*args, **kwargs)
        if self.instance and self.instance.pk:
            self.admins = self.instance.get_users(models.ProjectRole.ADMINISTRATOR)
            self.managers = self.instance.get_users(models.ProjectRole.MANAGER)
            self.support_users = self.instance.get_users(models.ProjectRole.SUPPORT)
            self.fields['admins'].initial = self.admins
            self.fields['managers'].initial = self.managers
            self.fields['support_users'].initial = self.support_users
        else:
            for field_name in ('admins', 'managers', 'support_users'):
                setattr(self, field_name, User.objects.none())

    def save(self, commit=True):
        project = super(ProjectAdminForm, self).save(commit=False)

        if not project.pk:
            project.save()

        self.populate_users('admins', project, models.ProjectRole.ADMINISTRATOR)
        self.populate_users('managers', project, models.ProjectRole.MANAGER)
        self.populate_users('support_users', project, models.ProjectRole.SUPPORT)

        return project

    def populate_users(self, field_name, project, role):
        field = getattr(self, field_name)
        new_users = self.cleaned_data[field_name]

        removed_users = field.exclude(pk__in=new_users)
        for user in removed_users:
            project.remove_user(user, role)

        added_users = new_users.exclude(pk__in=field)
        for user in added_users:
            # User role within project must be unique.
            if not project.has_user(user):
                project.add_user(user, role, self.request.user)
        self.save_m2m()

class ProjectAdmin(FormRequestAdminMixin,
                   ResourceCounterFormMixin,
                   ProtectedModelMixin,
                   ChangeReadonlyMixin,
                   admin.ModelAdmin):
    form = ProjectAdminForm

    fields = ('name', 'description', 'customer', 'admins', 'managers', 'support_users')

    list_display = ['name', 'uuid', 'customer', 'created', 'get_vm_count', 'get_app_count', 'get_private_cloud_count']
    search_fields = ['name', 'uuid']
    change_readonly_fields = ['customer']
    inlines = [QuotaInline]

class CertificationAdmin(admin.ModelAdmin):
    list_display = ('name', 'link')
    search_fields = ['name', 'link', 'description']

class ServiceSettingsAdminForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(ServiceSettingsAdminForm, self).__init__(*args, **kwargs)
        self.fields['type'] = ChoiceField(choices=SupportedServices.get_choices(),
                                          widget=RadioSelect)

class ServiceTypeFilter(SimpleListFilter):
    title = 'type'
    parameter_name = 'type'

    def lookups(self, request, model_admin):
        return SupportedServices.get_choices()

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(type=self.value())
        else:
            return queryset

class ServiceSettingsAdmin(ChangeReadonlyMixin, admin.ModelAdmin):
    readonly_fields = ('error_message',)
    list_display = ('name', 'customer', 'get_type_display', 'shared', 'state', 'error_message')
    list_filter = (ServiceTypeFilter, 'state', 'shared')
    change_readonly_fields = ('shared', 'customer')
    actions = ['pull', 'connect_shared']
    form = ServiceSettingsAdminForm
    fields = ('type', 'name', 'backend_url', 'username', 'password',
              'token', 'domain', 'certificate', 'options', 'customer',
              'shared', 'state', 'error_message', 'tags', 'homepage', 'terms_of_services', 'certifications')
    inlines = [QuotaInline]
    filter_horizontal = ('certifications',)

    def get_type_display(self, obj):
        return obj.get_type_display()
    get_type_display.short_description = 'Type'

    def add_view(self, *args, **kwargs):
        self.exclude = getattr(self, 'add_exclude', ())
        return super(ServiceSettingsAdmin, self).add_view(*args, **kwargs)

    def get_readonly_fields(self, request, obj=None):
        fields = super(ServiceSettingsAdmin, self).get_readonly_fields(request, obj)
        if not obj:
            return fields + ('state',)
        return fields

    def get_form(self, request, obj=None, **kwargs):
        # filter out certain fields from the creation form
        form = super(ServiceSettingsAdmin, self).get_form(request, obj, **kwargs)
        if 'shared' in form.base_fields:
            form.base_fields['shared'].initial = True
        return form

    def get_urls(self):
        my_urls = patterns(
            '',
            url(r'^(.+)/services/$', self.admin_site.admin_view(self.services)),
        )
        return my_urls + super(ServiceSettingsAdmin, self).get_urls()

    def services(self, request, pk=None):
        settings = models.ServiceSettings.objects.get(id=pk)
        projects = {}

        for name, model in SupportedServices.get_service_models().items():
            services = model['service'].objects.filter(settings=settings).values_list('id', flat=True)
            for spl in model['service_project_link'].objects.filter(service__in=services):
                projects.setdefault(spl.project.id, {
                    'name': six.text_type(spl.project),
                    'url': get_admin_url(spl.project),
                    'services': [],
                })
                projects[spl.project.id]['services'].append({
                    'name': six.text_type(spl.service),
                    'url': get_admin_url(spl.service),
                })

        return render(request, 'structure/service_settings_entities.html', {'projects': projects.values()})

    class Pull(ExecutorAdminAction):
        executor = executors.ServiceSettingsPullExecutor
        short_description = 'Pull'

        def validate(self, service_settings):
            States = models.ServiceSettings.States
            if service_settings.state not in (States.OK, States.ERRED):
                raise ValidationError('Service settings has to be OK or erred.')

    pull = Pull()

    class ConnectShared(ExecutorAdminAction):
        executor = executors.ServiceSettingsConnectSharedExecutor
        short_description = 'Create SPLs and services for shared service settings'

        def validate(self, service_settings):
            if not service_settings.shared:
                raise ValidationError('It is impossible to connect not shared settings')

    connect_shared = ConnectShared()

    def save_model(self, request, obj, form, change):
        obj.save()
        if not change:
            executors.ServiceSettingsCreateExecutor.execute(obj)

class ServiceAdmin(admin.ModelAdmin):
    list_display = ('customer', 'settings')
    ordering = ('customer',)

class ServiceProjectLinkAdmin(admin.ModelAdmin):
    readonly_fields = ('service', 'project')
    list_display = ('get_service_name', 'get_customer_name', 'get_project_name')
    list_filter = ('service__settings', 'project__name')
    ordering = ('service__customer__name', 'project__name')
    list_display_links = ('get_service_name',)
    search_fields = ('service__customer__name', 'project__name')
    inlines = [QuotaInline]

    def get_queryset(self, request):
        queryset = super(ServiceProjectLinkAdmin, self).get_queryset(request)
        return queryset.select_related('service', 'project', 'project__customer')

    def get_service_name(self, obj):
        return obj.service.settings.name

    get_service_name.short_description = 'Service'

    def get_project_name(self, obj):
        return obj.project.name

    get_project_name.short_description = 'Project'

    def get_customer_name(self, obj):
        return obj.service.customer.name

    get_customer_name.short_description = 'Customer'

class ResourceAdmin(admin.ModelAdmin):
    readonly_fields = ('error_message',)
    list_display = ('uuid', 'name', 'backend_id', 'state', 'created', 'get_service', 'get_project', 'error_message')
    list_filter = ('state',)

    def get_service(self, obj):
        return obj.service_project_link.service

    get_service.short_description = 'Service'
    get_service.admin_order_field = 'service_project_link__service__name'

    def get_project(self, obj):
        return obj.service_project_link.project

    get_project.short_description = 'Project'
    get_project.admin_order_field = 'service_project_link__project__name'

class PublishableResourceAdmin(ResourceAdmin):
    list_display = ResourceAdmin.list_display + ('publishing_state',)

class VirtualMachineAdmin(ResourceAdmin):
    readonly_fields = ResourceAdmin.readonly_fields + ('image_name',)

    actions = ['detect_coordinates']

    def detect_coordinates(self, request, queryset):
        send_task('structure', 'detect_vm_coordinates_batch')([utils.serialize_instance(vm) for vm in queryset])

        tasks_scheduled = queryset.count()
        message = ungettext(
            'Coordinates detection has been scheduled for one virtual machine',
            'Coordinates detection has been scheduled for %(tasks_scheduled)d virtual machines',
            tasks_scheduled
        )
        message = message % {'tasks_scheduled': tasks_scheduled}

        self.message_user(request, message)

    detect_coordinates.short_description = "Detect coordinates of virtual machines"

admin.site.register(models.Certification, CertificationAdmin)
admin.site.register(models.Customer, CustomerAdmin)
admin.site.register(models.Project, ProjectAdmin)
admin.site.register(models.ServiceSettings, ServiceSettingsAdmin)
<commit_msg>Allow sort and search per settings name [WAL-496]<commit_after>from django.conf.urls import patterns, url
from django.contrib import admin, messages
from django.contrib.admin import SimpleListFilter
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.core.exceptions import ValidationError
from django.db import models as django_models
from django.forms import ModelForm, ModelMultipleChoiceField, ChoiceField, RadioSelect
from django.http import HttpResponseRedirect
from django.utils import six
from django.utils.translation import ungettext
from django.shortcuts import render

from nodeconductor.core.admin import get_admin_url, ExecutorAdminAction
from nodeconductor.core.models import User
from nodeconductor.core.tasks import send_task
from nodeconductor.core import utils
from nodeconductor.quotas.admin import QuotaInline
from nodeconductor.structure import models, SupportedServices, executors

class FormRequestAdminMixin(object):
    """
    This mixin allows you to get current request user in the model admin form,
    which then passed to add_user method, so that user which granted role,
    is stored in the permission model.
    """
    def get_form(self, request, obj=None, **kwargs):
        form = super(FormRequestAdminMixin, self).get_form(request, obj=obj, **kwargs)
        form.request = request
        return form

class ChangeReadonlyMixin(object):

    add_readonly_fields = ()
    change_readonly_fields = ()

    def get_readonly_fields(self, request, obj=None):
        fields = super(ChangeReadonlyMixin, self).get_readonly_fields(request, obj)
        if hasattr(request, '_is_admin_add_view') and request._is_admin_add_view:
            return tuple(set(fields) | set(self.add_readonly_fields))
        else:
            return tuple(set(fields) | set(self.change_readonly_fields))

    def add_view(self, request, *args, **kwargs):
        request._is_admin_add_view = True
        return super(ChangeReadonlyMixin, self).add_view(request, *args, **kwargs)

class ProtectedModelMixin(object):
    def delete_view(self, request, *args, **kwargs):
        try:
            response = super(ProtectedModelMixin, self).delete_view(request, *args, **kwargs)
        except django_models.ProtectedError as e:
            self.message_user(request, e, messages.ERROR)
            return HttpResponseRedirect('.')
        else:
            return response

class ResourceCounterFormMixin(object):

    def get_vm_count(self, obj):
        return obj.quotas.get(name=obj.Quotas.nc_vm_count).usage

    get_vm_count.short_description = 'VM count'

    def get_app_count(self, obj):
        return obj.quotas.get(name=obj.Quotas.nc_app_count).usage

    get_app_count.short_description = 'Application count'

    def get_private_cloud_count(self, obj):
        return obj.quotas.get(name=obj.Quotas.nc_private_cloud_count).usage

    get_private_cloud_count.short_description = 'Private cloud count'

class CustomerAdminForm(ModelForm):
    owners = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                      widget=FilteredSelectMultiple(verbose_name='Owners', is_stacked=False))
    support_users = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                             widget=FilteredSelectMultiple(verbose_name='Support users',
                                                                           is_stacked=False))

    def __init__(self, *args, **kwargs):
        super(CustomerAdminForm, self).__init__(*args, **kwargs)
        if self.instance and self.instance.pk:
            self.owners = self.instance.get_owners()
            self.support_users = self.instance.get_support_users()
            self.fields['owners'].initial = self.owners
            self.fields['support_users'].initial = self.support_users
        else:
            self.owners = User.objects.none()
            self.support_users = User.objects.none()

    def save(self, commit=True):
        customer = super(CustomerAdminForm, self).save(commit=False)

        if not customer.pk:
            customer.save()

        self.populate_users('owners', customer, models.CustomerRole.OWNER)
        self.populate_users('support_users', customer, models.CustomerRole.SUPPORT)

        return customer

    def populate_users(self, field_name, customer, role):
        field = getattr(self, field_name)
        new_users = self.cleaned_data[field_name]

        removed_users = field.exclude(pk__in=new_users)
        for user in removed_users:
            customer.remove_user(user, role)

        added_users = new_users.exclude(pk__in=field)
        for user in added_users:
            # User role within customer must be unique.
            if not customer.has_user(user):
                customer.add_user(user, role, self.request.user)

        self.save_m2m()

class CustomerAdmin(FormRequestAdminMixin,
                    ResourceCounterFormMixin,
                    ProtectedModelMixin,
                    admin.ModelAdmin):
    form = CustomerAdminForm
    fields = ('name', 'image', 'native_name', 'abbreviation', 'contact_details', 'registration_code',
              'country', 'vat_code', 'is_company', 'balance', 'owners', 'support_users')
    readonly_fields = ['balance']
    list_display = ['name', 'uuid', 'abbreviation', 'created', 'get_vm_count', 'get_app_count',
                    'get_private_cloud_count']
    inlines = [QuotaInline]

class ProjectAdminForm(ModelForm):
    admins = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                      widget=FilteredSelectMultiple(verbose_name='Admins', is_stacked=False))
    managers = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                        widget=FilteredSelectMultiple(verbose_name='Managers', is_stacked=False))
    support_users = ModelMultipleChoiceField(User.objects.all().order_by('full_name'), required=False,
                                             widget=FilteredSelectMultiple(verbose_name='Support users',
                                                                           is_stacked=False))

    def __init__(self, *args, **kwargs):
        super(ProjectAdminForm, self).__init__(*args, **kwargs)
        if self.instance and self.instance.pk:
            self.admins = self.instance.get_users(models.ProjectRole.ADMINISTRATOR)
            self.managers = self.instance.get_users(models.ProjectRole.MANAGER)
            self.support_users = self.instance.get_users(models.ProjectRole.SUPPORT)
            self.fields['admins'].initial = self.admins
            self.fields['managers'].initial = self.managers
            self.fields['support_users'].initial = self.support_users
        else:
            for field_name in ('admins', 'managers', 'support_users'):
                setattr(self, field_name, User.objects.none())

    def save(self, commit=True):
        project = super(ProjectAdminForm, self).save(commit=False)

        if not project.pk:
            project.save()

        self.populate_users('admins', project, models.ProjectRole.ADMINISTRATOR)
        self.populate_users('managers', project, models.ProjectRole.MANAGER)
        self.populate_users('support_users', project, models.ProjectRole.SUPPORT)

        return project

    def populate_users(self, field_name, project, role):
        field = getattr(self, field_name)
        new_users = self.cleaned_data[field_name]

        removed_users = field.exclude(pk__in=new_users)
        for user in removed_users:
            project.remove_user(user, role)

        added_users = new_users.exclude(pk__in=field)
        for user in added_users:
            # User role within project must be unique.
            if not project.has_user(user):
                project.add_user(user, role, self.request.user)
        self.save_m2m()

class ProjectAdmin(FormRequestAdminMixin,
                   ResourceCounterFormMixin,
                   ProtectedModelMixin,
                   ChangeReadonlyMixin,
                   admin.ModelAdmin):
    form = ProjectAdminForm

    fields = ('name', 'description', 'customer', 'admins', 'managers', 'support_users')

    list_display = ['name', 'uuid', 'customer', 'created', 'get_vm_count', 'get_app_count', 'get_private_cloud_count']
    search_fields = ['name', 'uuid']
    change_readonly_fields = ['customer']
    inlines = [QuotaInline]

class CertificationAdmin(admin.ModelAdmin):
    list_display = ('name', 'link')
    search_fields = ['name', 'link', 'description']

class ServiceSettingsAdminForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(ServiceSettingsAdminForm, self).__init__(*args, **kwargs)
        self.fields['type'] = ChoiceField(choices=SupportedServices.get_choices(),
                                          widget=RadioSelect)

class ServiceTypeFilter(SimpleListFilter):
    title = 'type'
    parameter_name = 'type'

    def lookups(self, request, model_admin):
        return SupportedServices.get_choices()

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(type=self.value())
        else:
            return queryset

class ServiceSettingsAdmin(ChangeReadonlyMixin, admin.ModelAdmin):
    readonly_fields = ('error_message',)
    list_display = ('name', 'customer', 'get_type_display', 'shared', 'state', 'error_message')
    list_filter = (ServiceTypeFilter, 'state', 'shared')
    change_readonly_fields = ('shared', 'customer')
    actions = ['pull', 'connect_shared']
    form = ServiceSettingsAdminForm
    fields = ('type', 'name', 'backend_url', 'username', 'password',
              'token', 'domain', 'certificate', 'options', 'customer',
              'shared', 'state', 'error_message', 'tags', 'homepage', 'terms_of_services', 'certifications')
    inlines = [QuotaInline]
    filter_horizontal = ('certifications',)

    def get_type_display(self, obj):
        return obj.get_type_display()
    get_type_display.short_description = 'Type'

    def add_view(self, *args, **kwargs):
        self.exclude = getattr(self, 'add_exclude', ())
        return super(ServiceSettingsAdmin, self).add_view(*args, **kwargs)

    def get_readonly_fields(self, request, obj=None):
        fields = super(ServiceSettingsAdmin, self).get_readonly_fields(request, obj)
        if not obj:
            return fields + ('state',)
        return fields

    def get_form(self, request, obj=None, **kwargs):
        # filter out certain fields from the creation form
        form = super(ServiceSettingsAdmin, self).get_form(request, obj, **kwargs)
        if 'shared' in form.base_fields:
            form.base_fields['shared'].initial = True
        return form

    def get_urls(self):
        my_urls = patterns(
            '',
            url(r'^(.+)/services/$', self.admin_site.admin_view(self.services)),
        )
        return my_urls + super(ServiceSettingsAdmin, self).get_urls()

    def services(self, request, pk=None):
        settings = models.ServiceSettings.objects.get(id=pk)
        projects = {}

        for name, model in SupportedServices.get_service_models().items():
            services = model['service'].objects.filter(settings=settings).values_list('id', flat=True)
            for spl in model['service_project_link'].objects.filter(service__in=services):
                projects.setdefault(spl.project.id, {
                    'name': six.text_type(spl.project),
                    'url': get_admin_url(spl.project),
                    'services': [],
                })
                projects[spl.project.id]['services'].append({
                    'name': six.text_type(spl.service),
                    'url': get_admin_url(spl.service),
                })

        return render(request, 'structure/service_settings_entities.html', {'projects': projects.values()})

    class Pull(ExecutorAdminAction):
        executor = executors.ServiceSettingsPullExecutor
        short_description = 'Pull'

        def validate(self, service_settings):
            States = models.ServiceSettings.States
            if service_settings.state not in (States.OK, States.ERRED):
                raise ValidationError('Service settings has to be OK or erred.')

    pull = Pull()

    class ConnectShared(ExecutorAdminAction):
        executor = executors.ServiceSettingsConnectSharedExecutor
        short_description = 'Create SPLs and services for shared service settings'

        def validate(self, service_settings):
            if not service_settings.shared:
                raise ValidationError('It is impossible to connect not shared settings')

    connect_shared = ConnectShared()

    def save_model(self, request, obj, form, change):
        obj.save()
        if not change:
            executors.ServiceSettingsCreateExecutor.execute(obj)

class ServiceAdmin(admin.ModelAdmin):
    list_display = ('customer', 'settings')
    ordering = ('customer',)

class ServiceProjectLinkAdmin(admin.ModelAdmin):
    readonly_fields = ('service', 'project')
    list_display = ('get_service_name', 'get_customer_name', 'get_project_name')
    list_filter = ('service__settings', 'project__name', 'service__settings__name')
    ordering = ('service__customer__name', 'project__name')
    list_display_links = ('get_service_name',)
    search_fields = ('service__customer__name', 'project__name', 'service__settings__name')
    inlines = [QuotaInline]

    def get_queryset(self, request):
        queryset = super(ServiceProjectLinkAdmin, self).get_queryset(request)
        return queryset.select_related('service', 'project', 'project__customer')

    def get_service_name(self, obj):
        return obj.service.settings.name

    get_service_name.short_description = 'Service'

    def get_project_name(self, obj):
        return obj.project.name

    get_project_name.short_description = 'Project'

    def get_customer_name(self, obj):
        return obj.service.customer.name

    get_customer_name.short_description = 'Customer'

class ResourceAdmin(admin.ModelAdmin):
    readonly_fields = ('error_message',)
    list_display = ('uuid', 'name', 'backend_id', 'state', 'created', 'get_service', 'get_project', 'error_message')
    list_filter = ('state',)

    def get_service(self, obj):
        return obj.service_project_link.service

    get_service.short_description = 'Service'
    get_service.admin_order_field = 'service_project_link__service__name'

    def get_project(self, obj):
        return obj.service_project_link.project

    get_project.short_description = 'Project'
    get_project.admin_order_field = 'service_project_link__project__name'

class PublishableResourceAdmin(ResourceAdmin):
    list_display = ResourceAdmin.list_display + ('publishing_state',)

class VirtualMachineAdmin(ResourceAdmin):
    readonly_fields = ResourceAdmin.readonly_fields + ('image_name',)

    actions = ['detect_coordinates']

    def detect_coordinates(self, request, queryset):
        send_task('structure', 'detect_vm_coordinates_batch')([utils.serialize_instance(vm) for vm in queryset])

        tasks_scheduled = queryset.count()
        message = ungettext(
            'Coordinates detection has been scheduled for one virtual machine',
            'Coordinates detection has been scheduled for %(tasks_scheduled)d virtual machines',
            tasks_scheduled
        )
        message = message % {'tasks_scheduled': tasks_scheduled}

        self.message_user(request, message)

    detect_coordinates.short_description = "Detect coordinates of virtual machines"

admin.site.register(models.Certification, CertificationAdmin)
admin.site.register(models.Customer, CustomerAdmin)
admin.site.register(models.Project, ProjectAdmin)
admin.site.register(models.ServiceSettings, ServiceSettingsAdmin)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright (c) 2015 René Moser <mail@renemoser.net>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

class ModuleDocFragment(object):

    # Standard cloudstack documentation fragment
    DOCUMENTATION = '''
options:
  api_key:
    description:
      - API key of the CloudStack API.
    required: false
    default: null
    aliases: []
  api_secret:
    description:
      - Secret key of the CloudStack API.
    required: false
    default: null
    aliases: []
  api_url:
    description:
      - URL of the CloudStack API e.g. https://cloud.example.com/client/api.
    required: false
    default: null
    aliases: []
  api_http_method:
    description:
      - HTTP method used.
    required: false
    default: 'get'
    aliases: []
requirements:
  - "python >= 2.6"
  - cs
notes:
  - Ansible uses the C(cs) library's configuration method if credentials are not
    provided by the options C(api_url), C(api_key), C(api_secret).
    Configuration is read from several locations, in the following order":"
    - The C(CLOUDSTACK_ENDPOINT), C(CLOUDSTACK_KEY), C(CLOUDSTACK_SECRET) and
      C(CLOUDSTACK_METHOD) environment variables.
    - A C(CLOUDSTACK_CONFIG) environment variable pointing to an C(.ini) file,
    - A C(cloudstack.ini) file in the current working directory.
    - A C(.cloudstack.ini) file in the users home directory.
      See https://github.com/exoscale/cs for more information.
  - This module supports check mode.
'''
<commit_msg>cloudstack: minor cleanup in doc fragments<commit_after># -*- coding: utf-8 -*-
# Copyright (c) 2015 René Moser <mail@renemoser.net>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

class ModuleDocFragment(object):

    # Standard cloudstack documentation fragment
    DOCUMENTATION = '''
options:
  api_key:
    description:
      - API key of the CloudStack API.
    required: false
    default: null
  api_secret:
    description:
      - Secret key of the CloudStack API.
    required: false
    default: null
  api_url:
    description:
      - URL of the CloudStack API e.g. https://cloud.example.com/client/api.
    required: false
    default: null
  api_http_method:
    description:
      - HTTP method used.
    required: false
    default: 'get'
    choices: [ 'get', 'post' ]
requirements:
  - "python >= 2.6"
  - cs
notes:
  - Ansible uses the C(cs) library's configuration method if credentials are not
    provided by the options C(api_url), C(api_key), C(api_secret).
    Configuration is read from several locations, in the following order.
    - The C(CLOUDSTACK_ENDPOINT), C(CLOUDSTACK_KEY), C(CLOUDSTACK_SECRET) and
      C(CLOUDSTACK_METHOD) environment variables.
    - A C(CLOUDSTACK_CONFIG) environment variable pointing to an C(.ini) file,
    - A C(cloudstack.ini) file in the current working directory.
    - A C(.cloudstack.ini) file in the users home directory.
      See https://github.com/exoscale/cs for more information.
  - This module supports check mode.
'''
<|endoftext|>


<commit_before>import errno
import logging
import os
import shutil

from dvc.compat import fspath
from dvc.exceptions import DvcException

logger = logging.getLogger(__name__)

class System(object):
    @staticmethod
    def is_unix():
        return os.name != "nt"

    @staticmethod
    def copy(src, dest):
        src, dest = fspath(src), fspath(dest)
        return shutil.copyfile(src, dest)

    @staticmethod
    def hardlink(source, link_name):
        try:
            os.link(source, link_name)
            return
        except Exception as exc:
            raise DvcException("failed to link") from exc

    @staticmethod
    def symlink(source, link_name):
        try:
            os.symlink(source, link_name)
            return
        except Exception as exc:
            raise DvcException("failed to symlink") from exc

    @staticmethod
    def _reflink_darwin(src, dst):
        import ctypes

        LIBC = "libc.dylib"
        LIBC_FALLBACK = "/usr/lib/libSystem.dylib"
        try:
            clib = ctypes.CDLL(LIBC)
        except OSError as exc:
            logger.debug(
                "unable to access '{}' (errno '{}'). "
                "Falling back to '{}'.".format(LIBC, exc.errno, LIBC_FALLBACK)
            )
            if exc.errno != errno.ENOENT:
                raise
            # NOTE: trying to bypass System Integrity Protection (SIP)
            clib = ctypes.CDLL(LIBC_FALLBACK)

        if not hasattr(clib, "clonefile"):
            return -1

        clonefile = clib.clonefile
        clonefile.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
        clonefile.restype = ctypes.c_int

        return clonefile(
            ctypes.c_char_p(src.encode("utf-8")),
            ctypes.c_char_p(dst.encode("utf-8")),
            ctypes.c_int(0),
        )

    @staticmethod
    def _reflink_windows(src, dst):
        return -1

    @staticmethod
    def _reflink_linux(src, dst):
        import os
        import fcntl

        FICLONE = 0x40049409

        try:
            ret = 255
            with open(src, "r") as s, open(dst, "w+") as d:
                ret = fcntl.ioctl(d.fileno(), FICLONE, s.fileno())
        finally:
            if ret != 0:
                os.unlink(dst)

        return ret

    @staticmethod
    def reflink(source, link_name):
        import platform
        from dvc.exceptions import DvcException

        source, link_name = fspath(source), fspath(link_name)

        system = platform.system()
        try:
            if system == "Windows":
                ret = System._reflink_windows(source, link_name)
            elif system == "Darwin":
                ret = System._reflink_darwin(source, link_name)
            elif system == "Linux":
                ret = System._reflink_linux(source, link_name)
            else:
                ret = -1
        except IOError:
            ret = -1

        if ret != 0:
            raise DvcException("reflink is not supported")

    @staticmethod
    def _getdirinfo(path):
        from collections import namedtuple
        from win32file import (
            CreateFileW,
            GetFileInformationByHandle,
            FILE_FLAG_BACKUP_SEMANTICS,
            FILE_FLAG_OPEN_REPARSE_POINT,
            FILE_SHARE_READ,
            OPEN_EXISTING,
        )

        # NOTE: use FILE_FLAG_OPEN_REPARSE_POINT to open symlink itself and not
        # the target See https://docs.microsoft.com/en-us/windows/desktop/api/
        # fileapi/nf-fileapi-createfilew#symbolic-link-behavior
        flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

        hfile = CreateFileW(
            path, 0, FILE_SHARE_READ, None, OPEN_EXISTING, flags, None
        )

        # See BY_HANDLE_FILE_INFORMATION structure from fileapi.h
        Info = namedtuple(
            "BY_HANDLE_FILE_INFORMATION",
            [
                "dwFileAttributes",
                "ftCreationTime",
                "ftLastAccessTime",
                "ftLastWriteTime",
                "dwVolumeSerialNumber",
                "nFileSizeHigh",
                "nFileSizeLow",
                "nNumberOfLinks",
                "nFileIndexHigh",
                "nFileIndexLow",
            ],
        )

        return Info(*GetFileInformationByHandle(hfile))

    @staticmethod
    def inode(path):
        path = fspath(path)

        if System.is_unix():
            import ctypes

            inode = os.lstat(path).st_ino
            # NOTE: See https://bugs.python.org/issue29619 and
            # https://stackoverflow.com/questions/34643289/
            # pythons-os-stat-is-returning-wrong-inode-value
            inode = ctypes.c_ulong(inode).value
        else:
            # getdirinfo from ntfsutils works on both files and dirs
            info = System._getdirinfo(path)
            inode = abs(
                hash(
                    (
                        info.dwVolumeSerialNumber,
                        info.nFileIndexHigh,
                        info.nFileIndexLow,
                    )
                )
            )
        assert inode >= 0
        assert inode < 2 ** 64
        return inode

    @staticmethod
    def is_symlink(path):
        path = fspath(path)

        if System.is_unix():
            return os.path.islink(path)

        # https://docs.microsoft.com/en-us/windows/desktop/fileio/
        # file-attribute-constants
        from winnt import FILE_ATTRIBUTE_REPARSE_POINT

        if os.path.lexists(path):
            info = System._getdirinfo(path)
            return info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT
        return False

    @staticmethod
    def is_hardlink(path):
        path = fspath(path)

        if System.is_unix():
            return os.stat(path).st_nlink > 1

        info = System._getdirinfo(path)
        return info.nNumberOfLinks > 1
<commit_msg>system: catch OSError instead of Exception<commit_after>import errno
import logging
import os
import shutil

from dvc.compat import fspath
from dvc.exceptions import DvcException

logger = logging.getLogger(__name__)

class System(object):
    @staticmethod
    def is_unix():
        return os.name != "nt"

    @staticmethod
    def copy(src, dest):
        src, dest = fspath(src), fspath(dest)
        return shutil.copyfile(src, dest)

    @staticmethod
    def hardlink(source, link_name):
        try:
            os.link(source, link_name)
        except OSError as exc:
            raise DvcException("failed to link") from exc

    @staticmethod
    def symlink(source, link_name):
        try:
            os.symlink(source, link_name)
        except OSError as exc:
            raise DvcException("failed to symlink") from exc

    @staticmethod
    def _reflink_darwin(src, dst):
        import ctypes

        LIBC = "libc.dylib"
        LIBC_FALLBACK = "/usr/lib/libSystem.dylib"
        try:
            clib = ctypes.CDLL(LIBC)
        except OSError as exc:
            logger.debug(
                "unable to access '{}' (errno '{}'). "
                "Falling back to '{}'.".format(LIBC, exc.errno, LIBC_FALLBACK)
            )
            if exc.errno != errno.ENOENT:
                raise
            # NOTE: trying to bypass System Integrity Protection (SIP)
            clib = ctypes.CDLL(LIBC_FALLBACK)

        if not hasattr(clib, "clonefile"):
            return -1

        clonefile = clib.clonefile
        clonefile.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
        clonefile.restype = ctypes.c_int

        return clonefile(
            ctypes.c_char_p(src.encode("utf-8")),
            ctypes.c_char_p(dst.encode("utf-8")),
            ctypes.c_int(0),
        )

    @staticmethod
    def _reflink_windows(src, dst):
        return -1

    @staticmethod
    def _reflink_linux(src, dst):
        import os
        import fcntl

        FICLONE = 0x40049409

        try:
            ret = 255
            with open(src, "r") as s, open(dst, "w+") as d:
                ret = fcntl.ioctl(d.fileno(), FICLONE, s.fileno())
        finally:
            if ret != 0:
                os.unlink(dst)

        return ret

    @staticmethod
    def reflink(source, link_name):
        import platform
        from dvc.exceptions import DvcException

        source, link_name = fspath(source), fspath(link_name)

        system = platform.system()
        try:
            if system == "Windows":
                ret = System._reflink_windows(source, link_name)
            elif system == "Darwin":
                ret = System._reflink_darwin(source, link_name)
            elif system == "Linux":
                ret = System._reflink_linux(source, link_name)
            else:
                ret = -1
        except IOError:
            ret = -1

        if ret != 0:
            raise DvcException("reflink is not supported")

    @staticmethod
    def _getdirinfo(path):
        from collections import namedtuple
        from win32file import (
            CreateFileW,
            GetFileInformationByHandle,
            FILE_FLAG_BACKUP_SEMANTICS,
            FILE_FLAG_OPEN_REPARSE_POINT,
            FILE_SHARE_READ,
            OPEN_EXISTING,
        )

        # NOTE: use FILE_FLAG_OPEN_REPARSE_POINT to open symlink itself and not
        # the target See https://docs.microsoft.com/en-us/windows/desktop/api/
        # fileapi/nf-fileapi-createfilew#symbolic-link-behavior
        flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

        hfile = CreateFileW(
            path, 0, FILE_SHARE_READ, None, OPEN_EXISTING, flags, None
        )

        # See BY_HANDLE_FILE_INFORMATION structure from fileapi.h
        Info = namedtuple(
            "BY_HANDLE_FILE_INFORMATION",
            [
                "dwFileAttributes",
                "ftCreationTime",
                "ftLastAccessTime",
                "ftLastWriteTime",
                "dwVolumeSerialNumber",
                "nFileSizeHigh",
                "nFileSizeLow",
                "nNumberOfLinks",
                "nFileIndexHigh",
                "nFileIndexLow",
            ],
        )

        return Info(*GetFileInformationByHandle(hfile))

    @staticmethod
    def inode(path):
        path = fspath(path)

        if System.is_unix():
            import ctypes

            inode = os.lstat(path).st_ino
            # NOTE: See https://bugs.python.org/issue29619 and
            # https://stackoverflow.com/questions/34643289/
            # pythons-os-stat-is-returning-wrong-inode-value
            inode = ctypes.c_ulong(inode).value
        else:
            # getdirinfo from ntfsutils works on both files and dirs
            info = System._getdirinfo(path)
            inode = abs(
                hash(
                    (
                        info.dwVolumeSerialNumber,
                        info.nFileIndexHigh,
                        info.nFileIndexLow,
                    )
                )
            )
        assert inode >= 0
        assert inode < 2 ** 64
        return inode

    @staticmethod
    def is_symlink(path):
        path = fspath(path)

        if System.is_unix():
            return os.path.islink(path)

        # https://docs.microsoft.com/en-us/windows/desktop/fileio/
        # file-attribute-constants
        from winnt import FILE_ATTRIBUTE_REPARSE_POINT

        if os.path.lexists(path):
            info = System._getdirinfo(path)
            return info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT
        return False

    @staticmethod
    def is_hardlink(path):
        path = fspath(path)

        if System.is_unix():
            return os.stat(path).st_nlink > 1

        info = System._getdirinfo(path)
        return info.nNumberOfLinks > 1
<|endoftext|>


<commit_before>from Synopsis.Config import Base

class Config(Base):
    class Linker(Base.Linker):
        class SS(Base.Linker.Linker):
            comment_processors = ['ss']
        class Group(Base.Linker.Linker):
            comment_processors = ['group']
        class Dummy(Base.Linker.Linker):
            comment_processors = ['dummy']
        class Prev(Base.Linker.Linker):
            comment_processors = ['prev']
        modules = {'SS': SS,
                   'Group': Group,
                   'Dummy': Dummy,
                   'Prev': Prev}
<commit_msg>removed obsolete config<commit_after><|endoftext|>


<commit_before>#!/usr/bin/env python

###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

"""
Provides commands useful to the owner of the bot; the commands here require
their caller to have the 'owner' capability.  This plugin is loaded by default.
"""

__revision__ = "$Id$"

import fix

import gc
import os
import imp
import sys
import linecache

import conf
import debug
import utils
import world
import ircdb
import irclib
import ircmsgs
import drivers
import privmsgs
import callbacks

def loadPluginModule(name):
    """Loads (and returns) the module for the plugin with the given name."""
    files = []
    for dir in conf.pluginDirs:
        try:
            files.extend(os.listdir(dir))
        except EnvironmentError:
            debug.msg('Invalid plugin directory: %s' % dir, 'verbose')
    loweredFiles = map(str.lower, files)
    try:
        index = loweredFiles.index(name.lower()+'.py')
        name = os.path.splitext(files[index])[0]
    except ValueError: # We'd rather raise the ImportError, so we'll let go...
        pass
    moduleInfo = imp.find_module(name, conf.pluginDirs)
    module = imp.load_module(name, *moduleInfo)
    linecache.checkcache()
    return module

def loadPluginClass(irc, module):
    """Loads the plugin Class from the given module into the given irc."""
    callback = module.Class()
    assert not irc.getCallback(callback.name())
    irc.addCallback(callback)
    if hasattr(callback, 'configure'):
        callback.configure(irc)

class Owner(privmsgs.CapabilityCheckingPrivmsg):
    # This plugin must be first; its priority must be lowest; otherwise odd
    # things will happen when adding callbacks.
    priority = ~sys.maxint-1 # This must be first!
    capability = 'owner'
    _srcPlugins = ('Owner', 'Misc', 'Admin', 'User', 'Channel')
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        setattr(self.__class__, 'exec', self.__class__._exec)
        self.defaultPlugins = {'capabilities': 'User'}

    def disambiguate(self, irc, tokens, ambiguousCommands=None):
        if ambiguousCommands is None:
            ambiguousCommands = {}
        if tokens:
            command = callbacks.canonicalName(tokens[0])
            if command in self.defaultPlugins:
                tokens.insert(0, self.defaultPlugins[command])
            else:
                cbs = callbacks.findCallbackForCommand(irc, command)
                if len(cbs) > 1:
                    names = [cb.name() for cb in cbs]
                    srcs = [name for name in names if name in self._srcPlugins]
                    if len(srcs) == 1:
                        tokens.insert(0, srcs[0])
                    else:
                        ambiguousCommands[command] = names
            for elt in tokens:
                if isinstance(elt, list):
                    self.disambiguate(irc, elt, ambiguousCommands)

    def doPrivmsg(self, irc, msg):
        callbacks.Privmsg.handled = False
        if ircdb.checkIgnored(msg.prefix):
            return
        s = callbacks.addressed(irc.nick, msg)
        if s:
            try:
                tokens = callbacks.tokenize(s)
            except SyntaxError, e:
                irc.queueMsg(callbacks.error(msg, str(e)))
                return
            ambiguousCommands = {}
            self.disambiguate(irc, tokens, ambiguousCommands)
            if ambiguousCommands:
                if len(ambiguousCommands) == 1: # Common case.
                    (command, names) = ambiguousCommands.popitem()
                    names.sort()
                    s = 'The command %r is available in the %s plugins.  '\
                        'Please specify the plugin whose command you ' \
                        'wish to call by using its name as a command ' \
                        'before calling it.' % \
                        (command, utils.commaAndify(names))
                else:
                    L = []
                    for (command, names) in ambiguousCommands.iteritems():
                        names.sort()
                        L.append('The command %r is available in the %s '
                                 'plugins' %
                                 (command, utils.commaAndify(names)))
                    s = '%s; please specify from which plugins to ' \
                                 'call these commands.' % '; '.join(L)
                irc.queueMsg(callbacks.error(msg, s))
            else:
                callbacks.IrcObjectProxy(irc, msg, tokens)

    def defaultplugin(self, irc, msg, args):
        """<command> [<plugin>]

        Calls <command> from <plugin> by default, rather than complaining about
        multiple plugins providing it.  If <plugin> is not provided, remove the
        currently used default plugin.
        """
        (command, plugin) = privmsgs.getArgs(args, optional=1)
        command = callbacks.canonicalName(command)
        cbs = callbacks.findCallbackForCommand(irc, command)
        if not cbs:
            irc.error(msg, 'That\'t not a valid command.')
            return
        if plugin:
            self.defaultPlugins[command] = plugin
        else:
            try:
                del self.defaultPlugins[command]
            except KeyError:
                irc.error(msg, 'I have no default for that command.')
                return
        irc.reply(msg, conf.replySuccess)

    def eval(self, irc, msg, args):
        """<expression>

        Evaluates <expression> and returns its value.
        """
        if conf.allowEval:
            s = privmsgs.getArgs(args)
            try:
                irc.reply(msg, repr(eval(s)))
            except SyntaxError, e:
                irc.reply(msg, '%s: %r' % (debug.exnToString(e), s))
            except Exception, e:
                irc.reply(msg, debug.exnToString(e))
        else:
            irc.error(msg, conf.replyEvalNotAllowed)

    def _exec(self, irc, msg, args):
        """<statement>

        Execs <code>.  Returns success if it didn't raise any exceptions.
        """
        if conf.allowEval:
            s = privmsgs.getArgs(args)
            try:
                exec s
                irc.reply(msg, conf.replySuccess)
            except Exception, e:
                irc.reply(msg, debug.exnToString(e))
        else:
            irc.error(msg, conf.replyEvalNotAllowed)

    def setconf(self, irc, msg, args):
        """[<name> [<value>]]

        Lists adjustable variables in the conf-module by default, shows the
        variable type with only the <name> argument and sets the value of the
        variable to <value> when both arguments are given.
        """
        (name, value) = privmsgs.getArgs(args, required=0, optional=2)
        if name and value:
            if conf.allowEval:
                try:
                    value = eval(value)
                except Exception, e:
                    irc.error(msg, debug.exnToString(e))
                    return
                setattr(conf, name, value)
                irc.reply(msg, conf.replySuccess)
            else:
                if name == 'allowEval':
                    irc.error(msg, 'You can\'t set the value of allowEval.')
                    return
                elif name not in conf.types:
                    irc.error(msg, 'I can\'t set that conf variable.')
                    return
                else:
                    converter = conf.types[name]
                    try:
                        value = converter(value)
                    except ValueError, e:
                        irc.error(msg, str(e))
                        return
                    setattr(conf, name, value)
                    irc.reply(msg, conf.replySuccess)
        elif name:
            typeNames = {conf.mystr: 'string',
                         conf.mybool: 'boolean',
                         float: 'float'}
            try:
                type = typeNames[conf.types[name]]
            except KeyError:
                irc.error(msg, 'That configuration variable doesn\'t exist.')
                return
            try:
                value = getattr(conf, name)
                irc.reply(msg, '%s is a %s (%s).' % (name, type, value))
            except KeyError:
                irc.error(msg, '%s is of an unknown type.' % name)
        else:
            options = conf.types.keys()
            options.sort()
            irc.reply(msg, ', '.join(options))

    def setdefaultcapability(self, irc, msg, args):
        """<capability>

        Sets the default capability to be allowed for any command.
        """
        capability = callbacks.canonicalName(privmsgs.getArgs(args))
        conf.defaultCapabilities.add(capability)
        irc.reply(msg, conf.replySuccess)

    def unsetdefaultcapability(self, irc, msg, args):
        """<capability>

        Unsets the default capability for any command.
        """
        capability = callbacks.canonicalName(privmsgs.getArgs(args))
        conf.defaultCapabilities.remove(capability)
        irc.reply(msg, conf.replySuccess)

    def settrace(self, irc, msg, args):
        """takes no arguments

        Starts the function-tracing debug mode; beware that this makes *huge*
        logfiles.
        """
        sys.settrace(debug.tracer)
        irc.reply(msg, conf.replySuccess)

    def unsettrace(self, irc, msg, args):
        """takes no arguments

        Stops the function-tracing debug mode."""
        sys.settrace(None)
        irc.reply(msg, conf.replySuccess)

    def ircquote(self, irc, msg, args):
        """<string to be sent to the server>

        Sends the raw string given to the server.
        """
        s = privmsgs.getArgs(args)
        try:
            m = ircmsgs.IrcMsg(s)
            irc.queueMsg(m)
        except Exception:
            debug.recoverableException()
            irc.error(msg, conf.replyError)

    def quit(self, irc, msg, args):
        """[<int return value>]

        Exits the program with the given return value (the default is 0)
        """
        try:
            i = int(args[0])
        except (ValueError, IndexError):
            i = 0
        for driver in drivers._drivers.itervalues():
            driver.die()
        for irc in world.ircs[:]:
            irc.die()
        debug.exit(i)

    def flush(self, irc, msg, args):
        """takes no arguments

        Runs all the periodic flushers in world.flushers.
        """
        world.flush()
        irc.reply(msg, conf.replySuccess)

    def upkeep(self, irc, msg, args):
        """takes no arguments

        Runs the standard upkeep stuff (flushes and gc.collects()).
        """
        collected = world.upkeep()
        if gc.garbage:
            irc.reply(msg, 'Garbage!  %r' % gc.garbage)
        else:
            irc.reply(msg, '%s collected.' % utils.nItems(collected, 'object'))

    def set(self, irc, msg, args):
        """<name> <value>

        Sets the runtime variable <name> to <value>.  Currently used variables
        include "noflush" which, if set to true value, will prevent the
        periodic flushing that normally occurs.
        """
        (name, value) = privmsgs.getArgs(args, optional=1)
        world.tempvars[name] = value
        irc.reply(msg, conf.replySuccess)

    def unset(self, irc, msg, args):
        """<name>

        Unsets the value of variables set via the 'set' command.
        """
        name = privmsgs.getArgs(args)
        try:
            del world.tempvars[name]
            irc.reply(msg, conf.replySuccess)
        except KeyError:
            irc.error(msg, 'That variable wasn\'t set.')

    def load(self, irc, msg, args):
        """<plugin>

        Loads the plugin <plugin> from any of the directories in
        conf.pluginDirs; usually this includes the main installed directory
        and 'plugins' in the current directory.  Be sure not to have ".py" at
        the end.
        """
        name = privmsgs.getArgs(args)
        if irc.getCallback(name):
            irc.error(msg, 'That module is already loaded.')
            return
        try:
            module = loadPluginModule(name)
        except ImportError, e:
            if name in str(e):
                irc.error(msg, 'No plugin %s exists.' % name)
            else:
                irc.error(msg, debug.exnToString(e))
            return
        loadPluginClass(irc, module)
        irc.reply(msg, conf.replySuccess)

    def reload(self, irc, msg, args):
        """<plugin>

        Unloads and subsequently reloads the callback by name; use the 'list'
        command to see a list of the currently loaded callbacks.
        """
        name = privmsgs.getArgs(args)
        callbacks = irc.removeCallback(name)
        if callbacks:
            module = sys.modules[callbacks[0].__module__]
            if hasattr(module, 'reload'):
                x = module.reload()
            try:
                module = loadPluginModule(name)
                if hasattr(module, 'reload'):
                    module.reload(x)
                for callback in callbacks:
                    callback.die()
                    del callback
                gc.collect()
                callback = loadPluginClass(irc, module)
                irc.reply(msg, conf.replySuccess)
            except ImportError:
                for callback in callbacks:
                    irc.addCallback(callback)
                irc.error(msg, 'No plugin %s exists.' % name)
        else:
            irc.error(msg, 'There was no callback %s.' % name)

    def unload(self, irc, msg, args):
        """<plugin>

        Unloads the callback by name; use the 'list' command to see a list
        of the currently loaded callbacks.
        """
        name = privmsgs.getArgs(args)
        callbacks = irc.removeCallback(name)
        if callbacks:
            for callback in callbacks:
                callback.die()
                del callback
            gc.collect()
            irc.reply(msg, conf.replySuccess)
        else:
            irc.error(msg, 'There was no callback %s' % name)

    def reconf(self, irc, msg, args):
        """takes no arguments

        Reloads the configuration files in conf.dataDir: conf/users.conf and
        conf/channels.conf, by default.
        """
        ircdb.users.reload()
        ircdb.channels.reload()
        irc.reply(msg, conf.replySuccess)

Class = Owner

# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

<commit_msg>Made Admin the default for {add,remove}capability..<commit_after>#!/usr/bin/env python

###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

"""
Provides commands useful to the owner of the bot; the commands here require
their caller to have the 'owner' capability.  This plugin is loaded by default.
"""

__revision__ = "$Id$"

import fix

import gc
import os
import imp
import sys
import linecache

import conf
import debug
import utils
import world
import ircdb
import irclib
import ircmsgs
import drivers
import privmsgs
import callbacks

def loadPluginModule(name):
    """Loads (and returns) the module for the plugin with the given name."""
    files = []
    for dir in conf.pluginDirs:
        try:
            files.extend(os.listdir(dir))
        except EnvironmentError:
            debug.msg('Invalid plugin directory: %s' % dir, 'verbose')
    loweredFiles = map(str.lower, files)
    try:
        index = loweredFiles.index(name.lower()+'.py')
        name = os.path.splitext(files[index])[0]
    except ValueError: # We'd rather raise the ImportError, so we'll let go...
        pass
    moduleInfo = imp.find_module(name, conf.pluginDirs)
    module = imp.load_module(name, *moduleInfo)
    linecache.checkcache()
    return module

def loadPluginClass(irc, module):
    """Loads the plugin Class from the given module into the given irc."""
    callback = module.Class()
    assert not irc.getCallback(callback.name())
    irc.addCallback(callback)
    if hasattr(callback, 'configure'):
        callback.configure(irc)

class Owner(privmsgs.CapabilityCheckingPrivmsg):
    # This plugin must be first; its priority must be lowest; otherwise odd
    # things will happen when adding callbacks.
    priority = ~sys.maxint-1 # This must be first!
    capability = 'owner'
    _srcPlugins = ('Owner', 'Misc', 'Admin', 'User', 'Channel')
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        setattr(self.__class__, 'exec', self.__class__._exec)
        self.defaultPlugins = {'capabilities': 'User',
                               'addcapability': 'Admin',
                               'removecapability': 'Admin'}

    def disambiguate(self, irc, tokens, ambiguousCommands=None):
        if ambiguousCommands is None:
            ambiguousCommands = {}
        if tokens:
            command = callbacks.canonicalName(tokens[0])
            if command in self.defaultPlugins:
                tokens.insert(0, self.defaultPlugins[command])
            else:
                cbs = callbacks.findCallbackForCommand(irc, command)
                if len(cbs) > 1:
                    names = [cb.name() for cb in cbs]
                    srcs = [name for name in names if name in self._srcPlugins]
                    if len(srcs) == 1:
                        tokens.insert(0, srcs[0])
                    else:
                        ambiguousCommands[command] = names
            for elt in tokens:
                if isinstance(elt, list):
                    self.disambiguate(irc, elt, ambiguousCommands)

    def doPrivmsg(self, irc, msg):
        callbacks.Privmsg.handled = False
        if ircdb.checkIgnored(msg.prefix):
            return
        s = callbacks.addressed(irc.nick, msg)
        if s:
            try:
                tokens = callbacks.tokenize(s)
            except SyntaxError, e:
                irc.queueMsg(callbacks.error(msg, str(e)))
                return
            ambiguousCommands = {}
            self.disambiguate(irc, tokens, ambiguousCommands)
            if ambiguousCommands:
                if len(ambiguousCommands) == 1: # Common case.
                    (command, names) = ambiguousCommands.popitem()
                    names.sort()
                    s = 'The command %r is available in the %s plugins.  '\
                        'Please specify the plugin whose command you ' \
                        'wish to call by using its name as a command ' \
                        'before calling it.' % \
                        (command, utils.commaAndify(names))
                else:
                    L = []
                    for (command, names) in ambiguousCommands.iteritems():
                        names.sort()
                        L.append('The command %r is available in the %s '
                                 'plugins' %
                                 (command, utils.commaAndify(names)))
                    s = '%s; please specify from which plugins to ' \
                                 'call these commands.' % '; '.join(L)
                irc.queueMsg(callbacks.error(msg, s))
            else:
                callbacks.IrcObjectProxy(irc, msg, tokens)

    def defaultplugin(self, irc, msg, args):
        """<command> [<plugin>]

        Calls <command> from <plugin> by default, rather than complaining about
        multiple plugins providing it.  If <plugin> is not provided, remove the
        currently used default plugin.
        """
        (command, plugin) = privmsgs.getArgs(args, optional=1)
        command = callbacks.canonicalName(command)
        cbs = callbacks.findCallbackForCommand(irc, command)
        if not cbs:
            irc.error(msg, 'That\'t not a valid command.')
            return
        if plugin:
            self.defaultPlugins[command] = plugin
        else:
            try:
                del self.defaultPlugins[command]
            except KeyError:
                irc.error(msg, 'I have no default for that command.')
                return
        irc.reply(msg, conf.replySuccess)

    def eval(self, irc, msg, args):
        """<expression>

        Evaluates <expression> and returns its value.
        """
        if conf.allowEval:
            s = privmsgs.getArgs(args)
            try:
                irc.reply(msg, repr(eval(s)))
            except SyntaxError, e:
                irc.reply(msg, '%s: %r' % (debug.exnToString(e), s))
            except Exception, e:
                irc.reply(msg, debug.exnToString(e))
        else:
            irc.error(msg, conf.replyEvalNotAllowed)

    def _exec(self, irc, msg, args):
        """<statement>

        Execs <code>.  Returns success if it didn't raise any exceptions.
        """
        if conf.allowEval:
            s = privmsgs.getArgs(args)
            try:
                exec s
                irc.reply(msg, conf.replySuccess)
            except Exception, e:
                irc.reply(msg, debug.exnToString(e))
        else:
            irc.error(msg, conf.replyEvalNotAllowed)

    def setconf(self, irc, msg, args):
        """[<name> [<value>]]

        Lists adjustable variables in the conf-module by default, shows the
        variable type with only the <name> argument and sets the value of the
        variable to <value> when both arguments are given.
        """
        (name, value) = privmsgs.getArgs(args, required=0, optional=2)
        if name and value:
            if conf.allowEval:
                try:
                    value = eval(value)
                except Exception, e:
                    irc.error(msg, debug.exnToString(e))
                    return
                setattr(conf, name, value)
                irc.reply(msg, conf.replySuccess)
            else:
                if name == 'allowEval':
                    irc.error(msg, 'You can\'t set the value of allowEval.')
                    return
                elif name not in conf.types:
                    irc.error(msg, 'I can\'t set that conf variable.')
                    return
                else:
                    converter = conf.types[name]
                    try:
                        value = converter(value)
                    except ValueError, e:
                        irc.error(msg, str(e))
                        return
                    setattr(conf, name, value)
                    irc.reply(msg, conf.replySuccess)
        elif name:
            typeNames = {conf.mystr: 'string',
                         conf.mybool: 'boolean',
                         float: 'float'}
            try:
                type = typeNames[conf.types[name]]
            except KeyError:
                irc.error(msg, 'That configuration variable doesn\'t exist.')
                return
            try:
                value = getattr(conf, name)
                irc.reply(msg, '%s is a %s (%s).' % (name, type, value))
            except KeyError:
                irc.error(msg, '%s is of an unknown type.' % name)
        else:
            options = conf.types.keys()
            options.sort()
            irc.reply(msg, ', '.join(options))

    def setdefaultcapability(self, irc, msg, args):
        """<capability>

        Sets the default capability to be allowed for any command.
        """
        capability = callbacks.canonicalName(privmsgs.getArgs(args))
        conf.defaultCapabilities.add(capability)
        irc.reply(msg, conf.replySuccess)

    def unsetdefaultcapability(self, irc, msg, args):
        """<capability>

        Unsets the default capability for any command.
        """
        capability = callbacks.canonicalName(privmsgs.getArgs(args))
        conf.defaultCapabilities.remove(capability)
        irc.reply(msg, conf.replySuccess)

    def settrace(self, irc, msg, args):
        """takes no arguments

        Starts the function-tracing debug mode; beware that this makes *huge*
        logfiles.
        """
        sys.settrace(debug.tracer)
        irc.reply(msg, conf.replySuccess)

    def unsettrace(self, irc, msg, args):
        """takes no arguments

        Stops the function-tracing debug mode."""
        sys.settrace(None)
        irc.reply(msg, conf.replySuccess)

    def ircquote(self, irc, msg, args):
        """<string to be sent to the server>

        Sends the raw string given to the server.
        """
        s = privmsgs.getArgs(args)
        try:
            m = ircmsgs.IrcMsg(s)
            irc.queueMsg(m)
        except Exception:
            debug.recoverableException()
            irc.error(msg, conf.replyError)

    def quit(self, irc, msg, args):
        """[<int return value>]

        Exits the program with the given return value (the default is 0)
        """
        try:
            i = int(args[0])
        except (ValueError, IndexError):
            i = 0
        for driver in drivers._drivers.itervalues():
            driver.die()
        for irc in world.ircs[:]:
            irc.die()
        debug.exit(i)

    def flush(self, irc, msg, args):
        """takes no arguments

        Runs all the periodic flushers in world.flushers.
        """
        world.flush()
        irc.reply(msg, conf.replySuccess)

    def upkeep(self, irc, msg, args):
        """takes no arguments

        Runs the standard upkeep stuff (flushes and gc.collects()).
        """
        collected = world.upkeep()
        if gc.garbage:
            irc.reply(msg, 'Garbage!  %r' % gc.garbage)
        else:
            irc.reply(msg, '%s collected.' % utils.nItems(collected, 'object'))

    def set(self, irc, msg, args):
        """<name> <value>

        Sets the runtime variable <name> to <value>.  Currently used variables
        include "noflush" which, if set to true value, will prevent the
        periodic flushing that normally occurs.
        """
        (name, value) = privmsgs.getArgs(args, optional=1)
        world.tempvars[name] = value
        irc.reply(msg, conf.replySuccess)

    def unset(self, irc, msg, args):
        """<name>

        Unsets the value of variables set via the 'set' command.
        """
        name = privmsgs.getArgs(args)
        try:
            del world.tempvars[name]
            irc.reply(msg, conf.replySuccess)
        except KeyError:
            irc.error(msg, 'That variable wasn\'t set.')

    def load(self, irc, msg, args):
        """<plugin>

        Loads the plugin <plugin> from any of the directories in
        conf.pluginDirs; usually this includes the main installed directory
        and 'plugins' in the current directory.  Be sure not to have ".py" at
        the end.
        """
        name = privmsgs.getArgs(args)
        if irc.getCallback(name):
            irc.error(msg, 'That module is already loaded.')
            return
        try:
            module = loadPluginModule(name)
        except ImportError, e:
            if name in str(e):
                irc.error(msg, 'No plugin %s exists.' % name)
            else:
                irc.error(msg, debug.exnToString(e))
            return
        loadPluginClass(irc, module)
        irc.reply(msg, conf.replySuccess)

    def reload(self, irc, msg, args):
        """<plugin>

        Unloads and subsequently reloads the callback by name; use the 'list'
        command to see a list of the currently loaded callbacks.
        """
        name = privmsgs.getArgs(args)
        callbacks = irc.removeCallback(name)
        if callbacks:
            module = sys.modules[callbacks[0].__module__]
            if hasattr(module, 'reload'):
                x = module.reload()
            try:
                module = loadPluginModule(name)
                if hasattr(module, 'reload'):
                    module.reload(x)
                for callback in callbacks:
                    callback.die()
                    del callback
                gc.collect()
                callback = loadPluginClass(irc, module)
                irc.reply(msg, conf.replySuccess)
            except ImportError:
                for callback in callbacks:
                    irc.addCallback(callback)
                irc.error(msg, 'No plugin %s exists.' % name)
        else:
            irc.error(msg, 'There was no callback %s.' % name)

    def unload(self, irc, msg, args):
        """<plugin>

        Unloads the callback by name; use the 'list' command to see a list
        of the currently loaded callbacks.
        """
        name = privmsgs.getArgs(args)
        callbacks = irc.removeCallback(name)
        if callbacks:
            for callback in callbacks:
                callback.die()
                del callback
            gc.collect()
            irc.reply(msg, conf.replySuccess)
        else:
            irc.error(msg, 'There was no callback %s' % name)

    def reconf(self, irc, msg, args):
        """takes no arguments

        Reloads the configuration files in conf.dataDir: conf/users.conf and
        conf/channels.conf, by default.
        """
        ircdb.users.reload()
        ircdb.channels.reload()
        irc.reply(msg, conf.replySuccess)

Class = Owner

# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright 2015 Lorenzo Battistini <lorenzo.battistini@agilebg.com>
# Copyright 2017 Eficent <http://www.eficent.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, models

class MailThread(models.AbstractModel):
    _inherit = 'mail.thread'

    @api.model
    def message_route_verify(
        self, message, message_dict, route, update_author=True,
            assert_model=True, create_fallback=True, allow_private=False):
        res = ()
        try:
            res = super(MailThread, self).message_route_verify(
                message, message_dict, route,
                update_author=update_author, assert_model=assert_model,
                create_fallback=create_fallback, allow_private=allow_private)
        except ValueError as ve:
            fetchmail_server_id = self.env.context.get('fetchmail_server_id')
            if not fetchmail_server_id:
                raise ve
            fetchmail_server = self.pool['fetchmail.server'].browse(
                fetchmail_server_id)
            if not fetchmail_server.error_notice_template_id:
                raise ve
            self.env.context['sender_message'] = message
            self.env.context['route_exception'] = ve
            self.env['mail.template'].send_mail(
                fetchmail_server.error_notice_template_id.id,
                fetchmail_server.id)
            self.env.context['error_notice_sent'] = True
        return res

    @api.model
    def message_route(
        self, message, message_dict, model=None, thread_id=None,
            custom_values=None):
        res = []
        try:
            res = super(MailThread, self).message_route(
                message, message_dict, model=model,
                thread_id=thread_id, custom_values=custom_values)
        except ValueError as ve:
            if self.env.context.get('error_notice_sent'):
                # avoid raising exception and setting mail message UNSEEN
                return []
            else:
                raise ve
        return res
<commit_msg>porting fetchmail_notify_error_to_sender to 10.0<commit_after># -*- coding: utf-8 -*-
# Copyright 2015-2017 Lorenzo Battistini <lorenzo.battistini@agilebg.com>
# Copyright 2017 Eficent <http://www.eficent.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, models

class MailThread(models.AbstractModel):
    _inherit = 'mail.thread'

    @api.model
    def message_route(self, message, message_dict, model=None, thread_id=None,
                      custom_values=None):
        try:
            res = super(MailThread, self).message_route(
                message, message_dict, model=model, thread_id=thread_id,
                custom_values=custom_values)
        except ValueError as ve:
            fetchmail_server_id = self.env.context.get('fetchmail_server_id')
            if not fetchmail_server_id:
                raise ve
            fetchmail_server = self.env['fetchmail.server'].with_context({
                'sender_message': message,
                'route_exception': ve,
            }).browse(fetchmail_server_id)
            if not fetchmail_server.error_notice_template_id:
                raise ve
            fetchmail_server.error_notice_template_id.send_mail(
                fetchmail_server.id)
            raise ve
        return res
<|endoftext|>