Completed
Pull Request — master (#41)
by
unknown
05:38
created

submissions.views.FixValidation.post()   D

Complexity

Conditions 13

Size

Total Lines 47
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 36
dl 0
loc 47
rs 4.2
c 0
b 0
f 0
cc 13
nop 3

How to fix   Complexity   

Complexity

Complex classes like submissions.views.FixValidation.post() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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