Skip to content
Snippets Groups Projects
models.py 33.5 KiB
Newer Older
from datetime import date
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import JSONField
from django.core.exceptions import ValidationError, ObjectDoesNotExist, PermissionDenied
from django.core.files.storage import default_storage
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.db.models import Q
Todd Dembrey's avatar
Todd Dembrey committed
from django.db.models.expressions import RawSQL, OrderBy
from django.dispatch import receiver
from django.http import Http404
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.text import mark_safe, slugify
from django.utils.translation import ugettext_lazy as _
from django_fsm import can_proceed, FSMField, transition, RETURN_VALUE
Todd Dembrey's avatar
Todd Dembrey committed
from django_fsm.signals import post_transition
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from wagtail.admin.edit_handlers import (
    FieldPanel,
Dan Braghis's avatar
Dan Braghis committed
    InlinePanel,
    MultiFieldPanel,
Dan Braghis's avatar
Dan Braghis committed
    ObjectList,
    StreamFieldPanel,
Dan Braghis's avatar
Dan Braghis committed
    TabbedInterface,
from wagtail.admin.utils import send_mail
from wagtail.core.fields import StreamField
from wagtail.core.models import Orderable
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormSubmission
Todd Dembrey's avatar
Todd Dembrey committed

from opentech.apply.activity.messaging import messenger, MESSAGES
from opentech.apply.stream_forms.blocks import UploadableMediaBlock
from opentech.apply.stream_forms.models import AbstractStreamForm, BaseStreamForm
from opentech.apply.users.groups import REVIEWER_GROUP_NAME, STAFF_GROUP_NAME
from opentech.apply.utils.blocks import MustIncludeFieldBlock
from .admin_forms import WorkflowFormAdminForm
from .blocks import ApplicationCustomFormFieldsBlock, REQUIRED_BLOCK_NAMES
from .edit_handlers import FilteredFieldPanel, ReadOnlyPanel, ReadOnlyInlinePanel
from .workflow import (
    active_statuses,
    get_review_statuses,
    INITIAL_STATE,
    review_statuses,
    UserPermissions,
    WORKFLOWS,
Dan Braghis's avatar
Dan Braghis committed
    DETERMINATION_PHASES,
    DETERMINATION_RESPONSE_PHASES,
Dan Braghis's avatar
Dan Braghis committed
)
LIMIT_TO_STAFF = {'groups__name': STAFF_GROUP_NAME}
LIMIT_TO_REVIEWERS = {'groups__name': REVIEWER_GROUP_NAME}
LIMIT_TO_STAFF_AND_REVIEWERS = {'groups__name__in': [STAFF_GROUP_NAME, REVIEWER_GROUP_NAME]}


def admin_url(page):
    return reverse('wagtailadmin_pages:edit', args=(page.id,))


class SubmittableStreamForm(AbstractStreamForm):
    """
    Controls how stream forms are submitted. Any Page allowing submissions should inherit from here.
    """
    class Meta:
        abstract = True

    def get_submission_class(self):
        return ApplicationSubmission

    def process_form_submission(self, form):
        if not form.user.is_authenticated:
            form.user = None
        return self.get_submission_class().objects.create(
            form_data=form.cleaned_data,
            form_fields=self.get_defined_fields(),
            **self.get_submit_meta_data(user=form.user),
        )

    def get_submit_meta_data(self, **kwargs):
        return kwargs


class WorkflowHelpers(models.Model):
    Defines the common methods and fields for working with Workflows within Django models
    class Meta:
        abstract = True
    WORKFLOW_CHOICES = {
        name: workflow.name
        for name, workflow in WORKFLOWS.items()
    workflow_name = models.CharField(choices=WORKFLOW_CHOICES.items(), max_length=100, default='single', verbose_name="Workflow")
        return WORKFLOWS[self.workflow_name]
class WorkflowStreamForm(WorkflowHelpers, AbstractStreamForm):  # type: ignore
    """
    Defines the common methods and fields for working with Workflows within Wagtail pages
    """
    class Meta:
        abstract = True

    def get_defined_fields(self, stage=None):
        if not stage:
            form_index = 0
        else:
            form_index = self.workflow.stages.index(stage)
        return self.forms.all()[form_index].fields
    def render_landing_page(self, request, form_submission=None, *args, **kwargs):
        # We only reach this page after creation of a new submission
        # Hook in to notify about new applications
        messenger(
            request=request,
            user=form_submission.user,
            submission=form_submission,
        )
        return super().render_landing_page(request, form_submission=None, *args, **kwargs)

    content_panels = AbstractStreamForm.content_panels + [
        InlinePanel('review_forms', label="Review Forms")
    ]


class EmailForm(AbstractEmailForm):
    """
    Defines the behaviour for pages that hold information about emailing applicants

    Email Confirmation Panel should be included to allow admins to make changes.
    """
    class Meta:
        abstract = True

    confirmation_text_extra = models.TextField(blank=True, help_text="Additional text for the application confirmation message.")

    def process_form_submission(self, form):
        submission = super().process_form_submission(form)
        self.send_mail(submission)
    def send_mail(self, submission):
        user = submission.user
        context = {
            'name': user.get_full_name(),
            'email': user.email,
            'project_name': submission.form_data.get('title'),
            'extra_text': self.confirmation_text_extra,
            'fund_type': self.title,
        }

        subject = self.subject if self.subject else 'Thank you for your submission to Open Technology Fund'
        send_mail(subject, render_to_string('funds/email/confirmation.txt', context), (user.email,), self.from_address, )
Dan Braghis's avatar
Dan Braghis committed
    email_confirmation_panels = [
Dan Braghis's avatar
Dan Braghis committed
        MultiFieldPanel(
            [
                FieldRowPanel([
                    FieldPanel('from_address', classname="col6"),
                    FieldPanel('to_address', classname="col6"),
                ]),
                FieldPanel('subject'),
                FieldPanel('confirmation_text_extra'),
            ],
            heading="Confirmation email",
    email_tab = ObjectList(email_confirmation_panels, heading='Confirmation email')
class FundType(EmailForm, WorkflowStreamForm):  # type: ignore
    # Adds validation around forms & workflows. Isn't on Workflow class due to not displaying workflow field on Round
    base_form_class = WorkflowFormAdminForm

    reviewers = ParentalManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='fund_reviewers',
        limit_choices_to=LIMIT_TO_REVIEWERS,
Todd Dembrey's avatar
Todd Dembrey committed
        blank=True,
Loading
Loading full blame...