Passed
Pull Request — master (#41)
by
unknown
05:34
created

submissions.views.FixValidation.post()   B

Complexity

Conditions 5

Size

Total Lines 38
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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