from collections import defaultdict import logging 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 render, get_object_or_404 from django.urls import reverse from django.utils.translation import ugettext as _ from django.views.decorators.http import require_http_methods, require_safe from openach.auth import check_edit_authorization from openach.decorators import cache_if_anon, account_required from openach.forms import EvidenceForm, EvidenceSourceForm from openach.models import Board, Evidence, EvidenceSource, AnalystSourceTag, EvidenceSourceTag from openach.models import BoardFollower from openach.tasks import fetch_source_metadata from .util import remove_and_redirect from .notifications import notify_add, notify_edit 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) context = { '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) } return render(request, 'boards/evidence_detail.html', context) @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}) context = { 'board': board, 'evidence_form': evidence_form, 'source_form': source_form, } return render(request, 'boards/add_evidence.html', context) @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) context = { 'form': form, 'evidence': evidence, 'board': board, 'allow_remove': getattr(settings, 'EDIT_REMOVE_ENABLED', True), } return render(request, 'boards/edit_evidence.html', context) @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}) context = { 'form': form, 'evidence': evidence, 'corroborating': corroborating } return render(request, 'boards/add_source.html', context) @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,)))