Passed
Pull Request — master (#41)
by
unknown
06:08
created

SubmissionValidationSummaryView.get_context_data()   A

Complexity

Conditions 4

Size

Total Lines 21
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 21
rs 9.4
c 0
b 0
f 0
cc 4
nop 2
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
import ast
11
import re
12
from django.core.exceptions import ObjectDoesNotExist
13
14
from django.contrib import messages
15
from django.contrib.auth.mixins import LoginRequiredMixin
16
from django.db.models import Q
17
from django.http import HttpResponseRedirect
18
from django.views import View
19
from django.views.generic import (
20
    CreateView, DetailView, ListView, UpdateView, DeleteView)
21
from django.shortcuts import get_object_or_404, redirect
22
from django.urls import reverse_lazy, reverse
23
24
from common.constants import (
25
    WAITING, ERROR, SUBMITTED, NEED_REVISION, CRYOWEB_TYPE, CRB_ANIM_TYPE,
26
    TIME_UNITS, VALIDATION_MESSAGES_ATTRIBUTES, SAMPLE_STORAGE,
27
    SAMPLE_STORAGE_PROCESSING, ACCURACIES, ONTOLOGY_VALIDATION_MESSAGES,
28
    UNITS_VALIDATION_MESSAGES)
29
from common.helpers import get_deleted_objects, uid2biosample
30
from common.views import OwnerMixin
31
from crbanim.tasks import ImportCRBAnimTask
32
from cryoweb.tasks import import_from_cryoweb
33
34
from image_app.models import Submission, Name, Animal, Sample, DictUberon, \
35
    DictDevelStage, DictPhysioStage, DictSex, DictSpecie
36
from excel.tasks import ImportTemplateTask
37
38
from validation.helpers import construct_validation_message
39
from validation.models import ValidationSummary
40
from animals.tasks import BatchUpdateAnimals
41
from samples.tasks import BatchUpdateSamples
42
43
from .forms import SubmissionForm, ReloadForm
44
from .helpers import is_target_in_message
45
46
# Get an instance of a logger
47
logger = logging.getLogger(__name__)
48
49
50
class CreateSubmissionView(LoginRequiredMixin, CreateView):
51
    form_class = SubmissionForm
52
    model = Submission
53
54
    # template name is derived from model position and views type.
55
    # in this case, ir will be 'image_app/submission_form.html' so
56
    # i need to clearly specify it
57
    template_name = "submissions/submission_form.html"
58
59
    def form_invalid(self, form):
60
        messages.error(
61
            self.request,
62
            message="Please correct the errors below",
63
            extra_tags="alert alert-dismissible alert-danger")
64
65
        return super(CreateSubmissionView, self).form_invalid(form)
66
67
    # add user to this object
68
    def form_valid(self, form):
69
        self.object = form.save(commit=False)
70
        self.object.owner = self.request.user
71
72
        # I will have a different loading function accordingly with data type
73
        if self.object.datasource_type == CRYOWEB_TYPE:
74
            # update object and force status
75
            self.object.message = "waiting for data loading"
76
            self.object.status = WAITING
77
            self.object.save()
78
79
            # a valid submission start a task
80
            res = import_from_cryoweb.delay(self.object.pk)
81
            logger.info(
82
                "Start cryoweb importing process with task %s" % res.task_id)
83
84
        # I will have a different loading function accordingly with data type
85
        elif self.object.datasource_type == CRB_ANIM_TYPE:
86
            # update object and force status
87
            self.object.message = "waiting for data loading"
88
            self.object.status = WAITING
89
            self.object.save()
90
91
            # create a task
92
            my_task = ImportCRBAnimTask()
93
94
            # a valid submission start a task
95
            res = my_task.delay(self.object.pk)
96
            logger.info(
97
                "Start crbanim importing process with task %s" % res.task_id)
98
99
        else:
100
            # update object and force status
101
            self.object.message = "waiting for data loading"
102
            self.object.status = WAITING
103
            self.object.save()
104
105
            # create a task
106
            my_task = ImportTemplateTask()
107
108
            # a valid submission start a task
109
            res = my_task.delay(self.object.pk)
110
            logger.info(
111
                "Start template importing process with task %s" % res.task_id)
112
113
        # a redirect to self.object.get_absolute_url()
114
        return HttpResponseRedirect(self.get_success_url())
115
116
117
class MessagesSubmissionMixin(object):
118
    """Display messages in SubmissionViews"""
119
120
    # https://stackoverflow.com/a/45696442
121
    def get_context_data(self, **kwargs):
122
        data = super().get_context_data(**kwargs)
123
124
        # get the submission message
125
        message = self.submission.message
126
127
        # check if data are loaded or not
128
        if self.submission.status in [WAITING, SUBMITTED]:
129
            messages.warning(
130
                request=self.request,
131
                message=message,
132
                extra_tags="alert alert-dismissible alert-warning")
133
134
        elif self.submission.status in [ERROR, NEED_REVISION]:
135
            messages.error(
136
                request=self.request,
137
                message=message,
138
                extra_tags="alert alert-dismissible alert-danger")
139
140
        elif message is not None and message != '':
141
            messages.info(
142
                request=self.request,
143
                message=message,
144
                extra_tags="alert alert-dismissible alert-info")
145
146
        return data
147
148
149
class DetailSubmissionView(MessagesSubmissionMixin, OwnerMixin, DetailView):
150
    model = Submission
151
    template_name = "submissions/submission_detail.html"
152
153
    def get_context_data(self, **kwargs):
154
        # pass self.object to a new submission attribute in order to call
155
        # MessagesSubmissionMixin.get_context_data()
156
        self.submission = self.object
157
158
        # Call the base implementation first to get a context
159
        context = super(DetailSubmissionView, self).get_context_data(**kwargs)
160
161
        # add submission report to context
162
        validation_summary = construct_validation_message(self.submission)
163
164
        # HINT: is this computational intensive?
165
        context["validation_summary"] = validation_summary
166
167
        return context
168
169
170
class SubmissionValidationSummaryView(OwnerMixin, DetailView):
171
    model = Submission
172
    template_name = "submissions/submission_validation_summary.html"
173
174
    def get_context_data(self, **kwargs):
175
        context = super().get_context_data(**kwargs)
176
        summary_type = self.kwargs['type']
177
        try:
178
            validation_summary = self.object.validationsummary_set\
179
                .get(type=summary_type)
180
            context['validation_summary'] = validation_summary
181
            editable = list()
182
            for message in validation_summary.messages:
183
                message = ast.literal_eval(message)
184
                if uid2biosample(message['offending_column']) in \
185
                        [val for sublist in VALIDATION_MESSAGES_ATTRIBUTES for
186
                         val in sublist]:
187
                    editable.append(True)
188
                else:
189
                    editable.append(False)
190
            context['editable'] = editable
191
        except ObjectDoesNotExist:
192
            context['validation_summary'] = None
193
        context['submission'] = Submission.objects.get(pk=self.kwargs['pk'])
194
        return context
195
196
197
class SubmissionValidationSummaryFixErrorsView(OwnerMixin, ListView):
198
    template_name = "submissions/submission_validation_summary_fix_errors.html"
199
200
    def get_queryset(self):
201
        self.summary_type = self.kwargs['type']
202
        self.submission = Submission.objects.get(pk=self.kwargs['pk'])
203
        self.validation_summary = ValidationSummary.objects.get(
204
            submission=self.submission, type=self.summary_type)
205
        self.message = ast.literal_eval(self.validation_summary.messages[
206
                                            int(self.kwargs['message_counter'])
207
                                        ])
208
        self.offending_column = uid2biosample(
209
            self.message['offending_column'])
210
        if is_target_in_message(self.message['message'],
211
                                UNITS_VALIDATION_MESSAGES):
212
            self.show_units = True
213
            if self.offending_column == 'animal_age_at_collection':
214
                self.offending_column += "_units"
215
                self.units = [unit.name for unit in TIME_UNITS]
216
            elif self.offending_column == 'preparation_interval_units':
217
                self.units = [unit.name for unit in TIME_UNITS]
218
        elif re.search(".* of field .* is not in the valid values list .*",
219
                       self.message['message']):
220
            self.show_units = True
221
            if self.offending_column == 'storage':
222
                self.units = [unit.name for unit in SAMPLE_STORAGE]
223
            elif self.offending_column == 'storage_processing':
224
                self.units = [unit.name for unit in SAMPLE_STORAGE_PROCESSING]
225
            elif self.offending_column == 'collection_place_accuracy':
226
                self.units = [unit.name for unit in ACCURACIES]
227
            elif self.offending_column == 'birth_location_accuracy':
228
                self.units = [unit.name for unit in ACCURACIES]
229
        elif is_target_in_message(self.message['message'],
230
                                  ONTOLOGY_VALIDATION_MESSAGES):
231
            self.show_units = False
232
            self.units = None
233
            self.offending_column = 'term'
234
            if self.offending_column == 'organism_part':
235
                self.summary_type = 'dictuberon'
236
                return DictUberon.objects.filter(id__in=[Sample.objects.get(
237
                    pk=self.message['ids'][0]).organism_part_id])
238
            elif self.offending_column == 'developmental_stage':
239
                self.summary_type = 'dictdevelstage'
240
                return DictDevelStage.objects.filter(id__in=[Sample.objects.get(
241
                    pk=self.message['ids'][0]).developmental_stage_id])
242
            elif self.offending_column == 'physiological_stage':
243
                self.summary_type = 'dictphysiostage'
244
                return DictPhysioStage.objects.filter(id__in=[
245
                    Sample.objects.get(
246
                        pk=self.message['ids'][0]).physiological_stage_id])
247
            elif self.offending_column == 'sex':
248
                self.summary_type = 'dictsex'
249
                return DictSex.objects.filter(id__in=[Animal.objects.get(
250
                    pk=self.message['ids'][0]).sex_id])
251
            elif self.offending_column == 'species':
252
                self.summary_type = f'dictspecie_{self.summary_type}'
253
                if self.summary_type == 'dictspecie_animal':
254
                    return DictSpecie.objects.filter(id__in=[Animal.objects.get(
255
                        pk=self.message['ids'][0]).specie.id])
256
                elif self.summary_type == 'dictspecie_sample':
257
                    return DictSpecie.objects.filter(id__in=[Sample.objects.get(
258
                        pk=self.message['ids'][0]).specie.id])
259
        else:
260
            self.show_units = False
261
            self.units = None
262
        if self.summary_type == 'animal':
263
            return Animal.objects.filter(id__in=self.message['ids'])
264
        elif self.summary_type == 'sample':
265
            return Sample.objects.filter(id__in=self.message['ids'])
266
267
    def get_context_data(self, **kwargs):
268
        # Call the base implementation first to get a context
269
        context = super(
270
            SubmissionValidationSummaryFixErrorsView, self
271
        ).get_context_data(**kwargs)
272
273
        # add submission to context
274
        context["message"] = self.message
275
        context["type"] = self.summary_type
276
        context['attribute_to_edit'] = self.offending_column
277
        for attributes in VALIDATION_MESSAGES_ATTRIBUTES:
278
            if self.offending_column in attributes:
279
                context['attributes_to_show'] = [
280
                    attr for attr in attributes if attr != self.offending_column
281
                ]
282
        context['submission'] = self.submission
283
        context['error_type'] = 'coordinate_check'
284
        context['show_units'] = self.show_units
285
        if self.units:
286
            context['units'] = self.units
287
        return context
288
289
290
# a detail view since I need to operate on a submission object
291
# HINT: rename to a more informative name?
292
class EditSubmissionView(MessagesSubmissionMixin, OwnerMixin, ListView):
293
    template_name = "submissions/submission_edit.html"
294
    paginate_by = 10
295
296
    def get_queryset(self):
297
        """Subsetting names relying submission id"""
298
        self.submission = get_object_or_404(
299
            Submission,
300
            pk=self.kwargs['pk'],
301
            owner=self.request.user)
302
303
        # unknown animals should be removed from a submission. They have no
304
        # data in animal table nor sample
305
        return Name.objects.select_related(
306
                "validationresult",
307
                "animal",
308
                "sample").filter(
309
            Q(submission=self.submission) & (
310
                Q(animal__isnull=False) | Q(sample__isnull=False))
311
            ).order_by('id')
312
313 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...
314
        handler = super(EditSubmissionView, self).dispatch(
315
                request, *args, **kwargs)
316
317
        # here I've done get_queryset. Check for submission status
318
        if hasattr(self, "submission") and not self.submission.can_edit():
319
            message = "Cannot edit submission: current status is: %s" % (
320
                    self.submission.get_status_display())
321
322
            logger.warning(message)
323
            messages.warning(
324
                request=self.request,
325
                message=message,
326
                extra_tags="alert alert-dismissible alert-warning")
327
328
            return redirect(self.submission.get_absolute_url())
329
330
        return handler
331
332
    def get_context_data(self, **kwargs):
333
        # Call the base implementation first to get a context
334
        context = super(EditSubmissionView, self).get_context_data(**kwargs)
335
336
        # add submission to context
337
        context["submission"] = self.submission
338
339
        return context
340
341
342
class ListSubmissionsView(OwnerMixin, ListView):
343
    model = Submission
344
    template_name = "submissions/submission_list.html"
345
    ordering = ['-created_at']
346
    paginate_by = 10
347
348
349
class ReloadSubmissionView(OwnerMixin, UpdateView):
350
    form_class = ReloadForm
351
    model = Submission
352
    template_name = 'submissions/submission_reload.html'
353
354
    def form_invalid(self, form):
355
        messages.error(
356
            self.request,
357
            message="Please correct the errors below",
358
            extra_tags="alert alert-dismissible alert-danger")
359
360
        return super(ReloadSubmissionView, self).form_invalid(form)
361
362
    def form_valid(self, form):
363
        self.object = form.save(commit=False)
364
365
        # update object and force status
366
        self.object.message = "waiting for data loading"
367
        self.object.status = WAITING
368
        self.object.save()
369
370
        # HINT: can I change datasource type?
371
372
        # call the proper method
373
        if self.object.datasource_type == CRYOWEB_TYPE:
374
            # a valid submission start a task
375
            res = import_from_cryoweb.delay(self.object.pk)
376
            logger.info(
377
                "Start cryoweb reload process with task %s" % res.task_id)
378
379
        elif self.object.datasource_type == CRB_ANIM_TYPE:
380
            # a valid submission start a task
381
            my_task = ImportCRBAnimTask()
382
383
            # a valid submission start a task
384
            res = my_task.delay(self.object.pk)
385
            logger.info(
386
                "Start crbanim reload process with task %s" % res.task_id)
387
388
        else:
389
            # a valid submission start a task
390
            my_task = ImportTemplateTask()
391
392
            # a valid submission start a task
393
            res = my_task.delay(self.object.pk)
394
            logger.info(
395
                "Start template reload process with task %s" % res.task_id)
396
397
        # a redirect to self.object.get_absolute_url()
398
        return HttpResponseRedirect(self.get_success_url())
399
400
401
class DeleteSubmissionView(OwnerMixin, DeleteView):
402
    model = Submission
403
    template_name = "submissions/submission_confirm_delete.html"
404
    success_url = reverse_lazy('image_app:dashboard')
405
406 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...
407
        handler = super(DeleteSubmissionView, self).dispatch(
408
                request, *args, **kwargs)
409
410
        # here I've done get_queryset. Check for submission status
411
        if hasattr(self, "object") and not self.object.can_edit():
412
            message = "Cannot delete %s: submission status is: %s" % (
413
                    self.object, self.object.get_status_display())
414
415
            logger.warning(message)
416
            messages.warning(
417
                request=self.request,
418
                message=message,
419
                extra_tags="alert alert-dismissible alert-warning")
420
421
            return redirect(self.object.get_absolute_url())
422
423
        return handler
424
425
    # https://stackoverflow.com/a/39533619/4385116
426
    def get_context_data(self, **kwargs):
427
        # determining related objects
428
        # TODO: move this to a custom AJAX call
429
        context = super().get_context_data(**kwargs)
430
431
        deletable_objects, model_count, protected = get_deleted_objects(
432
            [self.object])
433
434
        # get only sample and animals from model_count
435
        info_deleted = {}
436
437
        items = ['animals', 'samples']
438
439
        for item in items:
440
            if item in model_count:
441
                info_deleted[item] = model_count[item]
442
443
        # add info to context
444
        context['info_deleted'] = dict(info_deleted).items()
445
446
        return context
447
448
    # https://ccbv.co.uk/projects/Django/1.11/django.views.generic.edit/DeleteView/#delete
449
    def delete(self, request, *args, **kwargs):
450
        """
451
        Add a message after calling base delete method
452
        """
453
454
        httpresponseredirect = super().delete(request, *args, **kwargs)
455
456
        message = "Submission %s was successfully deleted" % self.object.title
457
        logger.info(message)
458
459
        messages.info(
460
            request=self.request,
461
            message=message,
462
            extra_tags="alert alert-dismissible alert-info")
463
464
        return httpresponseredirect
465
466
467
class FixValidation(View, OwnerMixin):
468
    def post(self, request, **kwargs):
469
        # Fetch all required ids from input names and use it as keys
470
        keys_to_fix = dict()
471
        for key_to_fix in request.POST:
472
            if 'to_edit' in key_to_fix:
473
                keys_to_fix[
474
                    int(re.search('to_edit(.*)', key_to_fix).groups()[0])] \
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable int does not seem to be defined.
Loading history...
475
                    = request.POST[key_to_fix]
476
477
        pk = self.kwargs['pk']
478
        record_type = self.kwargs['record_type']
479
        attribute_to_edit = self.kwargs['attribute_to_edit']
480
481
        submission = Submission.objects.get(pk=pk)
482
        submission.message = "waiting for data updating"
483
        submission.status = WAITING
484
        submission.save()
485
486
        # Update validation summary
487
        if record_type == 'dictuberon' or record_type == 'dictdevelstage' or \
488
                record_type == 'dictphysiostage':
489
            validation_summary_type = 'sample'
490
        elif record_type == 'dictsex':
491
            validation_summary_type = 'animal'
492
        else:
493
            validation_summary_type = record_type
494
        summary_obj, created = ValidationSummary.objects.get_or_create(
495
            submission=submission, type=validation_summary_type)
496
        summary_obj.submission = submission
497
        summary_obj.reset()
498
499
        # create a task
500
        if record_type == 'animal' or record_type == 'dictsex' or \
501
                record_type == 'dictspecie_animal':
502
            my_task = BatchUpdateAnimals()
503
        elif record_type == 'sample' or record_type == 'dictuberon' or \
504
                record_type == 'dictdevelstage' or \
505
                record_type == 'dictphysiostage' or \
506
                record_type == 'dictspecie_sample':
507
            my_task = BatchUpdateSamples()
508
        else:
509
            return HttpResponseRedirect(
510
                reverse('submissions:detail', args=(pk,)))
511
512
        # a valid submission start a task
513
        res = my_task.delay(pk, keys_to_fix, attribute_to_edit, record_type)
514
        logger.info(
515
            "Start fix validation process with task %s" % res.task_id)
516
        return HttpResponseRedirect(reverse('submissions:detail', args=(pk,)))
517