Completed
Pull Request — master (#45)
by Paolo
06:21
created

submissions.views.BatchDelete.post()   A

Complexity

Conditions 4

Size

Total Lines 27
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 27
rs 9.328
c 0
b 0
f 0
cc 4
nop 4
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""
4
Created on Tue Jul 24 15:49:23 2018
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
import logging
10
from django.core.exceptions import ObjectDoesNotExist
11
12
from django.contrib import messages
13
from django.contrib.auth.mixins import LoginRequiredMixin
14
from django.db.models import Q
15
from django.http import HttpResponseRedirect, HttpResponse
16
from django.views.generic import (
17
    CreateView, DetailView, ListView, UpdateView, DeleteView, View)
18
from django.shortcuts import get_object_or_404, redirect, render
19
from django.urls import reverse_lazy, reverse
20
21
from common.constants import (
22
    WAITING, ERROR, SUBMITTED, NEED_REVISION, CRYOWEB_TYPE, CRB_ANIM_TYPE)
23
from common.helpers import get_deleted_objects
24
from common.views import OwnerMixin
25
from crbanim.tasks import ImportCRBAnimTask
26
from cryoweb.tasks import import_from_cryoweb
27
from image_app.models import Submission, Name
28
from excel.tasks import ImportTemplateTask
29
from validation.helpers import construct_validation_message
30
from validation.models import ValidationSummary
31
from animals.tasks import BatchDeleteAnimals
32
from samples.tasks import BatchDeleteSamples
33
34
from .forms import SubmissionForm, ReloadForm
35
36
# Get an instance of a logger
37
logger = logging.getLogger(__name__)
38
39
40
class CreateSubmissionView(LoginRequiredMixin, CreateView):
41
    form_class = SubmissionForm
42
    model = Submission
43
44
    # template name is derived from model position and views type.
45
    # in this case, ir will be 'image_app/submission_form.html' so
46
    # i need to clearly specify it
47
    template_name = "submissions/submission_form.html"
48
49
    def form_invalid(self, form):
50
        messages.error(
51
            self.request,
52
            message="Please correct the errors below",
53
            extra_tags="alert alert-dismissible alert-danger")
54
55
        return super(CreateSubmissionView, self).form_invalid(form)
56
57
    # add user to this object
58
    def form_valid(self, form):
59
        self.object = form.save(commit=False)
60
        self.object.owner = self.request.user
61
62
        # I will have a different loading function accordingly with data type
63
        if self.object.datasource_type == CRYOWEB_TYPE:
64
            # update object and force status
65
            self.object.message = "waiting for data loading"
66
            self.object.status = WAITING
67
            self.object.save()
68
69
            # a valid submission start a task
70
            res = import_from_cryoweb.delay(self.object.pk)
71
            logger.info(
72
                "Start cryoweb importing process with task %s" % res.task_id)
73
74
        # I will have a different loading function accordingly with data type
75
        elif self.object.datasource_type == CRB_ANIM_TYPE:
76
            # update object and force status
77
            self.object.message = "waiting for data loading"
78
            self.object.status = WAITING
79
            self.object.save()
80
81
            # create a task
82
            my_task = ImportCRBAnimTask()
83
84
            # a valid submission start a task
85
            res = my_task.delay(self.object.pk)
86
            logger.info(
87
                "Start crbanim importing process with task %s" % res.task_id)
88
89
        else:
90
            # update object and force status
91
            self.object.message = "waiting for data loading"
92
            self.object.status = WAITING
93
            self.object.save()
94
95
            # create a task
96
            my_task = ImportTemplateTask()
97
98
            # a valid submission start a task
99
            res = my_task.delay(self.object.pk)
100
            logger.info(
101
                "Start template importing process with task %s" % res.task_id)
102
103
        # a redirect to self.object.get_absolute_url()
104
        return HttpResponseRedirect(self.get_success_url())
105
106
107
class MessagesSubmissionMixin(object):
108
    """Display messages in SubmissionViews"""
109
110
    # https://stackoverflow.com/a/45696442
111
    def get_context_data(self, **kwargs):
112
        data = super().get_context_data(**kwargs)
113
114
        # get the submission message
115
        message = self.submission.message
116
117
        # check if data are loaded or not
118
        if self.submission.status in [WAITING, SUBMITTED]:
119
            messages.warning(
120
                request=self.request,
121
                message=message,
122
                extra_tags="alert alert-dismissible alert-warning")
123
124
        elif self.submission.status in [ERROR, NEED_REVISION]:
125
            messages.error(
126
                request=self.request,
127
                message=message,
128
                extra_tags="alert alert-dismissible alert-danger")
129
130
        elif message is not None and message != '':
131
            messages.info(
132
                request=self.request,
133
                message=message,
134
                extra_tags="alert alert-dismissible alert-info")
135
136
        return data
137
138
139
class DetailSubmissionView(MessagesSubmissionMixin, OwnerMixin, DetailView):
140
    model = Submission
141
    template_name = "submissions/submission_detail.html"
142
143
    def get_context_data(self, **kwargs):
144
        # pass self.object to a new submission attribute in order to call
145
        # MessagesSubmissionMixin.get_context_data()
146
        self.submission = self.object
147
148
        # Call the base implementation first to get a context
149
        context = super(DetailSubmissionView, self).get_context_data(**kwargs)
150
151
        # add submission report to context
152
        validation_summary = construct_validation_message(self.submission)
153
154
        # HINT: is this computational intensive?
155
        context["validation_summary"] = validation_summary
156
157
        return context
158
159
160
class SubmissionValidationSummaryView(OwnerMixin, DetailView):
161
    model = Submission
162
    template_name = "submissions/submission_validation_summary.html"
163
164
    def get_context_data(self, **kwargs):
165
        context = super().get_context_data(**kwargs)
166
        summary_type = ''
167
        if self.kwargs['type'] == 'animals':
168
            summary_type = 'animal'
169
        elif self.kwargs['type'] == 'samples':
170
            summary_type = 'sample'
171
        try:
172
            context['validation_summary'] = self.object.validationsummary_set\
173
                .get(type=summary_type)
174
        except ObjectDoesNotExist:
175
            context['validation_summary'] = None
176
        return context
177
178
179
# a detail view since I need to operate on a submission object
180
# HINT: rename to a more informative name?
181
class EditSubmissionView(MessagesSubmissionMixin, OwnerMixin, ListView):
182
    template_name = "submissions/submission_edit.html"
183
    paginate_by = 10
184
185
    def get_queryset(self):
186
        """Subsetting names relying submission id"""
187
        self.submission = get_object_or_404(
188
            Submission,
189
            pk=self.kwargs['pk'],
190
            owner=self.request.user)
191
192
        # unknown animals should be removed from a submission. They have no
193
        # data in animal table nor sample
194
        return Name.objects.select_related(
195
                "validationresult",
196
                "animal",
197
                "sample").filter(
198
            Q(submission=self.submission) & (
199
                Q(animal__isnull=False) | Q(sample__isnull=False))
200
            ).order_by('id')
201
202 View Code Duplication
    def dispatch(self, request, *args, **kwargs):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
203
        handler = super(EditSubmissionView, self).dispatch(
204
                request, *args, **kwargs)
205
206
        # here I've done get_queryset. Check for submission status
207
        if hasattr(self, "submission") and not self.submission.can_edit():
208
            message = "Cannot edit submission: current status is: %s" % (
209
                    self.submission.get_status_display())
210
211
            logger.warning(message)
212
            messages.warning(
213
                request=self.request,
214
                message=message,
215
                extra_tags="alert alert-dismissible alert-warning")
216
217
            return redirect(self.submission.get_absolute_url())
218
219
        return handler
220
221
    def get_context_data(self, **kwargs):
222
        # Call the base implementation first to get a context
223
        context = super(EditSubmissionView, self).get_context_data(**kwargs)
224
225
        # add submission to context
226
        context["submission"] = self.submission
227
228
        return context
229
230
231
class ListSubmissionsView(OwnerMixin, ListView):
232
    model = Submission
233
    template_name = "submissions/submission_list.html"
234
    ordering = ['-created_at']
235
    paginate_by = 10
236
237
238
class ReloadSubmissionView(OwnerMixin, UpdateView):
239
    form_class = ReloadForm
240
    model = Submission
241
    template_name = 'submissions/submission_reload.html'
242
243
    def form_invalid(self, form):
244
        messages.error(
245
            self.request,
246
            message="Please correct the errors below",
247
            extra_tags="alert alert-dismissible alert-danger")
248
249
        return super(ReloadSubmissionView, self).form_invalid(form)
250
251
    def form_valid(self, form):
252
        self.object = form.save(commit=False)
253
254
        # update object and force status
255
        self.object.message = "waiting for data loading"
256
        self.object.status = WAITING
257
        self.object.save()
258
259
        # HINT: can I change datasource type?
260
261
        # call the proper method
262
        if self.object.datasource_type == CRYOWEB_TYPE:
263
            # a valid submission start a task
264
            res = import_from_cryoweb.delay(self.object.pk)
265
            logger.info(
266
                "Start cryoweb reload process with task %s" % res.task_id)
267
268
        elif self.object.datasource_type == CRB_ANIM_TYPE:
269
            # a valid submission start a task
270
            my_task = ImportCRBAnimTask()
271
272
            # a valid submission start a task
273
            res = my_task.delay(self.object.pk)
274
            logger.info(
275
                "Start crbanim reload process with task %s" % res.task_id)
276
277
        else:
278
            # a valid submission start a task
279
            my_task = ImportTemplateTask()
280
281
            # a valid submission start a task
282
            res = my_task.delay(self.object.pk)
283
            logger.info(
284
                "Start template reload process with task %s" % res.task_id)
285
286
        # a redirect to self.object.get_absolute_url()
287
        return HttpResponseRedirect(self.get_success_url())
288
289
290
class DeleteAnimalsView(OwnerMixin, View):
291
    template = 'submissions/submission_batch_delete.html'
292
293
    def get(self, request, *args, **kwargs):
294
        pk = self.kwargs['pk']
295
        return render(request, self.template, {'delete_type': 'Animals',
296
                                               'pk': pk})
297
298
299
class DeleteSamplesView(OwnerMixin, View):
300
    template = 'submissions/submission_batch_delete.html'
301
302
    def get(self, request, *args, **kwargs):
303
        pk = self.kwargs['pk']
304
        return render(request, self.template, {'delete_type': 'Samples',
305
                                               'pk': pk})
306
307
308
class BatchDelete(OwnerMixin, View):
309
    def post(self, request, *args, **kwargs):
310
        pk = self.kwargs['pk']
311
        delete_type = self.kwargs['type']
312
        keys_to_delete = set()
313
        for key in request.POST['to_delete'].split('\n'):
314
            keys_to_delete.add(key.rstrip())
315
316
        submission = Submission.objects.get(pk=pk)
317
        submission.message = 'waiting for batch delete to complete'
318
        submission.status = WAITING
319
        submission.save()
320
321
        if delete_type == 'Animals':
322
            # Batch delete task for animals
323
            my_task = BatchDeleteAnimals()
324
            summary_obj, created = ValidationSummary.objects.get_or_create(
325
                submission=submission, type='animal')
326
        elif delete_type == 'Samples':
327
            # Batch delete task for samples
328
            my_task = BatchDeleteSamples()
329
            summary_obj, created = ValidationSummary.objects.get_or_create(
330
                submission=submission, type='sample')
331
        summary_obj.reset()
0 ignored issues
show
introduced by
The variable summary_obj does not seem to be defined for all execution paths.
Loading history...
332
        res = my_task.delay(pk, [item for item in keys_to_delete])
0 ignored issues
show
introduced by
The variable my_task does not seem to be defined for all execution paths.
Loading history...
333
        logger.info(
334
            "Start fix validation process with task %s" % res.task_id)
335
        return HttpResponseRedirect(reverse('submissions:detail', args=(pk,)))
336
337
338
class DeleteSubmissionView(OwnerMixin, DeleteView):
339
    model = Submission
340
    template_name = "submissions/submission_confirm_delete.html"
341
    success_url = reverse_lazy('image_app:dashboard')
342
343 View Code Duplication
    def dispatch(self, request, *args, **kwargs):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
344
        handler = super(DeleteSubmissionView, self).dispatch(
345
                request, *args, **kwargs)
346
347
        # here I've done get_queryset. Check for submission status
348
        if hasattr(self, "object") and not self.object.can_edit():
349
            message = "Cannot delete %s: submission status is: %s" % (
350
                    self.object, self.object.get_status_display())
351
352
            logger.warning(message)
353
            messages.warning(
354
                request=self.request,
355
                message=message,
356
                extra_tags="alert alert-dismissible alert-warning")
357
358
            return redirect(self.object.get_absolute_url())
359
360
        return handler
361
362
    # https://stackoverflow.com/a/39533619/4385116
363
    def get_context_data(self, **kwargs):
364
        # determining related objects
365
        # TODO: move this to a custom AJAX call
366
        context = super().get_context_data(**kwargs)
367
368
        deletable_objects, model_count, protected = get_deleted_objects(
369
            [self.object])
370
371
        # get only sample and animals from model_count
372
        info_deleted = {}
373
374
        items = ['animals', 'samples']
375
376
        for item in items:
377
            if item in model_count:
378
                info_deleted[item] = model_count[item]
379
380
        # add info to context
381
        context['info_deleted'] = dict(info_deleted).items()
382
383
        return context
384
385
    # https://ccbv.co.uk/projects/Django/1.11/django.views.generic.edit/DeleteView/#delete
386
    def delete(self, request, *args, **kwargs):
387
        """
388
        Add a message after calling base delete method
389
        """
390
391
        httpresponseredirect = super().delete(request, *args, **kwargs)
392
393
        message = "Submission %s was successfully deleted" % self.object.title
394
        logger.info(message)
395
396
        messages.info(
397
            request=self.request,
398
            message=message,
399
            extra_tags="alert alert-dismissible alert-info")
400
401
        return httpresponseredirect
402