aldryn/aldryn-jobs

View on GitHub
aldryn_jobs/admin.py

Summary

Maintainability
A
1 hr
Test Coverage
# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from django.conf import settings
from django.contrib import admin
from django.db import models
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _

from adminsortable2.admin import SortableAdminMixin
from aldryn_apphooks_config.admin import BaseAppHookConfig
from aldryn_translation_tools.admin import (
    AllTranslationsMixin,
    LinkedRelatedInlineMixin,
)

from cms.admin.placeholderadmin import PlaceholderAdminMixin, FrontendEditableAdminMixin

from emailit.api import send_mail
from parler.admin import TranslatableAdmin


from .forms import JobCategoryAdminForm, JobOpeningAdminForm
from .models import JobApplication, JobCategory, JobOpening, JobsConfig


def _send_rejection_email(modeladmin, request, queryset, lang_code='',
                          delete_application=False):
    # 1. send rejection email - this should be refactored to use djangos "bulk"
    #    mail
    #
    # Info: Using mass rejection on many JobApplications can lead to a timeout,
    # since SMTPs are not known to be fast
    qs_count = len(queryset)

    for application in queryset:
        context = {'job_application': application, }
        send_mail(recipients=[application.email], context=context,
                  template_base='aldryn_jobs/emails/rejection_letter',
                  language=lang_code.lower())

    # 2. update status or delete objects
    if not delete_application:
        queryset.update(is_rejected=True, rejection_date=now())
        success_msg = _("Successfully sent {0} rejection email(s).").format(
            qs_count)
    else:
        queryset.delete()
        success_msg = _("Successfully deleted {0} application(s) and sent "
                        "rejection email.").format(qs_count)

    # 3. inform user with success message
    modeladmin.message_user(request, success_msg)
    return


class SendRejectionEmail(object):

    def __init__(self, lang_code=''):
        super(SendRejectionEmail, self).__init__()
        self.lang_code = lang_code.upper()
        self.name = 'send_rejection_email_{0}'.format(self.lang_code)
        self.title = _("Send rejection e-mail {0}").format(self.lang_code)

    def __call__(self, modeladmin, request, queryset, *args, **kwargs):
        _send_rejection_email(modeladmin, request, queryset,
                              lang_code=self.lang_code)


class SendRejectionEmailAndDelete(SendRejectionEmail):

    def __init__(self, lang_code=''):
        super(SendRejectionEmailAndDelete, self).__init__(lang_code)
        self.name = 'send_rejection_and_delete_{0}'.format(self.lang_code)
        self.title = _("Send rejection e-mail and delete "
                       "application {0}").format(self.lang_code)

    def __call__(self, modeladmin, request, queryset, *args, **kwargs):
        _send_rejection_email(modeladmin, request, queryset,
                              lang_code=self.lang_code, delete_application=True)


class JobApplicationAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
    list_display = ['__str__', 'job_opening', 'created', 'is_rejected',
                    'rejection_date', ]
    list_filter = ['job_opening', 'is_rejected']
    readonly_fields = ['get_attachment_address']
    raw_id_fields = ['job_opening']

    fieldsets = [
        (_('Job Opening'), {
            'fields': ['job_opening']
        }),
        (_('Personal information'), {
            'fields': ['salutation', 'first_name', 'last_name', 'email']
        }),
        (_('Cover letter & attachments'), {
            'fields': ['cover_letter', 'get_attachment_address']
        })
    ]

    def get_actions(self, request):
        actions = super(JobApplicationAdmin, self).get_actions(request)
        language_codes = [language[0] for language in settings.LANGUAGES]
        for lang_code in language_codes:
            send_rejection_email = SendRejectionEmail(lang_code=lang_code)
            actions[send_rejection_email.name] = (
                send_rejection_email,
                send_rejection_email.name,
                send_rejection_email.title
            )
            send_rejection_and_delete = SendRejectionEmailAndDelete(
                lang_code=lang_code)
            actions[send_rejection_and_delete.name] = (
                send_rejection_and_delete,
                send_rejection_and_delete.name,
                send_rejection_and_delete.title
            )
        return actions

    def has_add_permission(self, request):
        # Don't allow creation of "new" applications via admin-backend until
        # it's properly implemented
        return False

    def get_attachment_address(self, instance):
        attachment_link = '<a href="{address}">{address}</a>'
        attachments = []

        for attachment in instance.attachments.all():
            if attachment:
                attachments.append(
                    attachment_link.format(address=attachment.file.url))
        return mark_safe('<br>'.join(attachments)) if attachments else '-'

    get_attachment_address.allow_tags = True
    get_attachment_address.short_description = _('Attachments')


class JobCategoryAdmin(PlaceholderAdminMixin,
                       SortableAdminMixin, AllTranslationsMixin,
                       TranslatableAdmin):
    form = JobCategoryAdminForm
    list_display = ['__str__', 'app_config']
    filter_horizontal = ['supervisors']

    def get_fieldsets(self, request, obj=None):
        fieldsets = [
            (_('Translatable fields'), {
                'fields': ['name', 'slug']
            }),
            (_('Supervisors'), {
                'fields': ['supervisors']
            }),
            (_('Options'), {
                'fields': ['app_config']
            })
        ]
        return fieldsets


class JobApplicationInline(LinkedRelatedInlineMixin, admin.TabularInline):
    model = JobApplication
    fields = ['email', 'is_rejected', ]
    readonly_fields = ['email', 'is_rejected', ]

    def has_add_permission(self, request):
        return False


class JobOpeningAdmin(PlaceholderAdminMixin,
                      AllTranslationsMixin,
                      SortableAdminMixin,
                      FrontendEditableAdminMixin,
                      TranslatableAdmin):
    form = JobOpeningAdminForm
    list_display = ['__str__', 'category', 'num_applications', ]
    frontend_editable_fields = ('title', 'lead_in')
    inlines = [JobApplicationInline, ]

    def get_fieldsets(self, request, obj=None):
        fieldsets = [
            (_('Translatable fields'), {
                'fields': ['title', 'slug', 'lead_in']
            }),
            (_('Options'), {
                'fields': ['category', 'is_active', 'can_apply']
            }),
            (_('Publication period'), {
                'fields': [('publication_start', 'publication_end')]
            })
        ]
        return fieldsets

    def get_queryset(self, request):
        qs = super(JobOpeningAdmin, self).get_queryset(request)
        qs = qs.annotate(applications_count=models.Count('applications'))
        return qs

    def num_applications(self, obj):
        return obj.applications_count
    num_applications.short_description = '# Applications'
    num_applications.admin_order_field = 'applications_count'


class JobsConfigAdmin(PlaceholderAdminMixin, BaseAppHookConfig):
    pass


admin.site.register(JobApplication, JobApplicationAdmin)
admin.site.register(JobCategory, JobCategoryAdmin)
admin.site.register(JobOpening, JobOpeningAdmin)
admin.site.register(JobsConfig, JobsConfigAdmin)