twschiller/open-synthesis

View on GitHub
openach/views/evidence.py

Summary

Maintainability
A
1 hr
Test Coverage
import logging
from collections import defaultdict

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.decorators.http import require_http_methods, require_safe

from openach.auth import check_edit_authorization
from openach.decorators import account_required, cache_if_anon
from openach.forms import EvidenceForm, EvidenceSourceForm
from openach.models import (
    AnalystSourceTag,
    Board,
    BoardFollower,
    Evidence,
    EvidenceSource,
    EvidenceSourceTag,
)
from openach.tasks import fetch_source_metadata

from .notifications import notify_add, notify_edit
from .util import remove_and_redirect

PAGE_CACHE_TIMEOUT_SECONDS = getattr(settings, "PAGE_CACHE_TIMEOUT_SECONDS", 60)

logger = logging.getLogger(__name__)  # pylint: disable=invalid-name


@require_safe
@account_required
@cache_if_anon(PAGE_CACHE_TIMEOUT_SECONDS)
def evidence_detail(request, evidence_id):
    """Return a view displaying detailed information about a piece of evidence and its sources."""
    # NOTE: cannot cache page for logged in users b/c comments section contains CSRF and other protection mechanisms.
    evidence = get_object_or_404(Evidence, pk=evidence_id)
    available_tags = EvidenceSourceTag.objects.all()
    sources = (
        EvidenceSource.objects.filter(evidence=evidence)
        .order_by("-source_date")
        .select_related("uploader")
    )
    all_tags = AnalystSourceTag.objects.filter(source__in=sources)

    source_tags = defaultdict(list)
    user_tags = defaultdict(list)
    for tag in all_tags:
        key = (tag.source_id, tag.tag_id)
        source_tags[key].append(tag)
        if tag.tagger_id == request.user.id:
            user_tags[key].append(tag)

    return render(
        request,
        "boards/evidence_detail.html",
        {
            "evidence": evidence,
            "sources": sources,
            "source_tags": source_tags,
            "user_tags": user_tags,
            "available_tags": available_tags,
            "meta_description": _("Analysis of evidence: {description}").format(
                description=evidence.evidence_desc
            ),
        },
    )


@require_http_methods(["HEAD", "GET", "POST"])
@login_required
def add_evidence(request, board_id):
    """Return a view of adding evidence (with a source), or handle the form submission."""
    board = get_object_or_404(Board, pk=board_id)

    if "add_elements" not in board.permissions.for_user(request.user):
        raise PermissionDenied()

    require_source = getattr(settings, "`EVIDENCE_REQUIRE_SOURCE`", True)

    if request.method == "POST":
        evidence_form = EvidenceForm(request.POST)
        source_form = EvidenceSourceForm(request.POST, require=require_source)
        if evidence_form.is_valid() and source_form.is_valid():
            with transaction.atomic():
                evidence = evidence_form.save(commit=False)
                evidence.board = board
                evidence.creator = request.user
                evidence.save()

                if source_form.cleaned_data.get("source_url"):
                    source = source_form.save(commit=False)
                    source.evidence = evidence
                    source.uploader = request.user
                    source.save()
                    fetch_source_metadata.delay(source.id)

                BoardFollower.objects.update_or_create(
                    board=board,
                    user=request.user,
                    defaults={
                        "is_contributor": True,
                    },
                )
            notify_add(board, actor=request.user, action_object=evidence)
            return HttpResponseRedirect(reverse("openach:detail", args=(board.id,)))
    else:
        evidence_form = EvidenceForm()
        source_form = EvidenceSourceForm(
            require=require_source, initial={"corroborating": True}
        )

    return render(
        request,
        "boards/add_evidence.html",
        {
            "board": board,
            "evidence_form": evidence_form,
            "source_form": source_form,
        },
    )


@require_http_methods(["HEAD", "GET", "POST"])
@login_required
def edit_evidence(request, evidence_id):
    """Return a view for editing a piece of evidence, or handle for submission."""
    evidence = get_object_or_404(Evidence, pk=evidence_id)
    # don't care that the board might have been removed
    board = evidence.board
    check_edit_authorization(request, board=board, has_creator=evidence)

    if request.method == "POST":
        form = EvidenceForm(request.POST, instance=evidence)
        if "remove" in form.data:
            return remove_and_redirect(request, evidence, evidence.evidence_desc)

        elif form.is_valid():
            form.save()
            messages.success(request, _("Updated evidence description and date."))
            notify_edit(board, actor=request.user, action_object=evidence)
            return HttpResponseRedirect(
                reverse("openach:evidence_detail", args=(evidence.id,))
            )

    else:
        form = EvidenceForm(instance=evidence)

    return render(
        request,
        "boards/edit_evidence.html",
        {
            "form": form,
            "evidence": evidence,
            "board": board,
            "allow_remove": getattr(settings, "EDIT_REMOVE_ENABLED", True),
        },
    )


@require_http_methods(["HEAD", "GET", "POST"])
@login_required
def add_source(request, evidence_id):
    """Return a view for adding a corroborating/contradicting source, or handle form submission."""
    evidence = get_object_or_404(Evidence, pk=evidence_id)
    if request.method == "POST":
        form = EvidenceSourceForm(request.POST)
        if form.is_valid():
            source = form.save(commit=False)
            source.evidence = evidence
            source.uploader = request.user
            source.save()
            fetch_source_metadata.delay(source.id)
            return HttpResponseRedirect(
                reverse("openach:evidence_detail", args=(evidence_id,))
            )
        else:
            corroborating = form.data["corroborating"] == "True"
    else:
        corroborating = (
            request.GET.get("kind") is None or request.GET.get("kind") != "conflicting"
        )
        form = EvidenceSourceForm(initial={"corroborating": corroborating})

    return render(
        request,
        "boards/add_source.html",
        {"form": form, "evidence": evidence, "corroborating": corroborating},
    )


@require_http_methods(["HEAD", "GET", "POST"])
@login_required
def toggle_source_tag(request, evidence_id, source_id):
    """Toggle source tag for the given source and redirect to the evidence detail page for the associated evidence."""
    # May want to put in a sanity check here that source_id actually corresponds to evidence_id
    # Inefficient to have to do the DB lookup before making a modification. May want to have the client pass in
    # whether or not they're adding/removing the tag
    if request.method == "POST":
        with transaction.atomic():
            source = get_object_or_404(EvidenceSource, pk=source_id)
            tag = EvidenceSourceTag.objects.get(tag_name=request.POST["tag"])
            user_tag = AnalystSourceTag.objects.filter(
                source=source, tagger=request.user, tag=tag
            )
            if user_tag.count() > 0:
                user_tag.delete()
                messages.success(
                    request,
                    _('Removed "{name}" tag from source.').format(name=tag.tag_name),
                )
            else:
                AnalystSourceTag.objects.create(
                    source=source, tagger=request.user, tag=tag
                )
                messages.success(
                    request,
                    _('Added "{name}" tag to source.').format(name=tag.tag_name),
                )
            return HttpResponseRedirect(
                reverse("openach:evidence_detail", args=(evidence_id,))
            )
    else:
        # Redirect to the form where the user can toggle a source tag
        return HttpResponseRedirect(
            reverse("openach:evidence_detail", args=(evidence_id,))
        )