Passed
Pull Request — master (#41)
by
unknown
01:42
created

SubmissionValidationSummaryFixErrorsView.get_queryset()   A

Complexity

Conditions 5

Size

Total Lines 15
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 15
rs 9.1832
c 0
b 0
f 0
cc 5
nop 1
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.generic import (
19
    CreateView, DetailView, ListView, UpdateView, DeleteView)
20
from django.shortcuts import get_object_or_404, redirect
21
from django.urls import reverse_lazy, reverse
22
23
from common.constants import (
24
    WAITING, ERROR, SUBMITTED, NEED_REVISION, CRYOWEB_TYPE, CRB_ANIM_TYPE,
25
    VALIDATION_MESSAGES, VALIDATION_MESSAGES_ATTRIBUTES)
26
from common.helpers import get_deleted_objects
27
from common.views import OwnerMixin
28
from cryoweb.tasks import import_from_cryoweb
29
from image_app.models import Submission, Name, Animal, Sample
30
from crbanim.tasks import ImportCRBAnimTask
31
from validation.helpers import construct_validation_message
32
from validation.models import ValidationSummary
33
from animals.tasks import BatchUpdateAnimals
34
from samples.tasks import BatchUpdateSamples
35
36
from .forms import SubmissionForm, ReloadForm
37
38
# Get an instance of a logger
39
logger = logging.getLogger(__name__)
40
41
42
class CreateSubmissionView(LoginRequiredMixin, CreateView):
43
    form_class = SubmissionForm
44
    model = Submission
45
46
    # template name is derived from model position and views type.
47
    # in this case, ir will be 'image_app/submission_form.html' so
48
    # i need to clearly specify it
49
    template_name = "submissions/submission_form.html"
50
51
    def form_invalid(self, form):
52
        messages.error(
53
            self.request,
54
            message="Please correct the errors below",
55
            extra_tags="alert alert-dismissible alert-danger")
56
57
        return super(CreateSubmissionView, self).form_invalid(form)
58
59
    # add user to this object
60
    def form_valid(self, form):
61
        self.object = form.save(commit=False)
62
        self.object.owner = self.request.user
63
64
        # I will have a different loading function accordingly with data type
65
        if self.object.datasource_type == CRYOWEB_TYPE:
66
            # update object and force status
67
            self.object.message = "waiting for data loading"
68
            self.object.status = WAITING
69
            self.object.save()
70
71
            # a valid submission start a task
72
            res = import_from_cryoweb.delay(self.object.pk)
73
            logger.info(
74
                "Start cryoweb importing process with task %s" % res.task_id)
75
76
        # I will have a different loading function accordingly with data type
77
        elif self.object.datasource_type == CRB_ANIM_TYPE:
78
            # update object and force status
79
            self.object.message = "waiting for data loading"
80
            self.object.status = WAITING
81
            self.object.save()
82
83
            # create a task
84
            my_task = ImportCRBAnimTask()
85
86
            # a valid submission start a task
87
            res = my_task.delay(self.object.pk)
88
            logger.info(
89
                "Start crbanim importing process with task %s" % res.task_id)
90
91
        else:
92
            message = "{datasource} import is not implemented".format(
93
                datasource=self.object.get_datasource_type_display())
94
95
            self.object.message = message
96
            self.object.status = ERROR
97
            self.object.save()
98
99
            logger.error(message)
100
101
        # a redirect to self.object.get_absolute_url()
102
        return HttpResponseRedirect(self.get_success_url())
103
104
105
class MessagesSubmissionMixin(object):
106
    """Display messages in SubmissionViews"""
107
108
    # https://stackoverflow.com/a/45696442
109
    def get_context_data(self, **kwargs):
110
        data = super().get_context_data(**kwargs)
111
112
        # get the submission message
113
        message = self.submission.message
114
115
        # check if data are loaded or not
116
        if self.submission.status in [WAITING, SUBMITTED]:
117
            messages.warning(
118
                request=self.request,
119
                message=message,
120
                extra_tags="alert alert-dismissible alert-warning")
121
122
        elif self.submission.status in [ERROR, NEED_REVISION]:
123
            messages.error(
124
                request=self.request,
125
                message=message,
126
                extra_tags="alert alert-dismissible alert-danger")
127
128
        elif message is not None and message != '':
129
            messages.info(
130
                request=self.request,
131
                message=message,
132
                extra_tags="alert alert-dismissible alert-info")
133
134
        return data
135
136
137
class DetailSubmissionView(MessagesSubmissionMixin, OwnerMixin, DetailView):
138
    model = Submission
139
    template_name = "submissions/submission_detail.html"
140
141
    def get_context_data(self, **kwargs):
142
        # pass self.object to a new submission attribute in order to call
143
        # MessagesSubmissionMixin.get_context_data()
144
        self.submission = self.object
145
146
        # Call the base implementation first to get a context
147
        context = super(DetailSubmissionView, self).get_context_data(**kwargs)
148
149
        # add submission report to context
150
        validation_summary = construct_validation_message(self.submission)
151
152
        # HINT: is this computational intensive?
153
        context["validation_summary"] = validation_summary
154
155
        return context
156
157
158
class SubmissionValidationSummaryView(OwnerMixin, DetailView):
159
    model = Submission
160
    template_name = "submissions/submission_validation_summary.html"
161
162
    def get_context_data(self, **kwargs):
163
        context = super().get_context_data(**kwargs)
164
        summary_type = self.kwargs['type']
165
        try:
166
            context['validation_summary'] = self.object.validationsummary_set\
167
                .get(type=summary_type)
168
        except ObjectDoesNotExist:
169
            context['validation_summary'] = None
170
        context['submission'] = Submission.objects.get(pk=self.kwargs['pk'])
171
        return context
172
173
174
class SubmissionValidationSummaryFixErrorsView(OwnerMixin, ListView):
175
    template_name = "submissions/submission_validation_summary_fix_errors.html"
176
177
    def get_queryset(self):
178
        ids = list()
179
        self.summary_type = self.kwargs['type']
180
        self.submission = Submission.objects.get(pk=self.kwargs['pk'])
181
        validation_summary = ValidationSummary.objects.get(
182
            submission=self.submission, type=self.summary_type)
183
        self.request_message = self.kwargs['msg']
184
        for message in validation_summary.messages:
185
            message = ast.literal_eval(message)
186
            if message['message'] == self.request_message:
187
                ids = message['ids']
188
        if self.summary_type == 'animal':
189
            return Animal.objects.filter(id__in=ids)
190
        elif self.summary_type == 'sample':
191
            return Sample.objects.filter(id__in=ids)
192
193
    def get_context_data(self, **kwargs):
194
        # Call the base implementation first to get a context
195
        context = super(
196
            SubmissionValidationSummaryFixErrorsView, self
197
        ).get_context_data(**kwargs)
198
199
        # add submission to context
200
        context["message"] = self.request_message
201
        context["type"] = self.summary_type
202
203
        if re.search(VALIDATION_MESSAGES['coordinate_check'],
204
                     self.request_message):
205
            type_of_check = f"coordinate_check_{self.summary_type}"
206
            context['attributes_to_show'] = VALIDATION_MESSAGES_ATTRIBUTES[
207
                type_of_check]['attributes_to_show']
208
            context['attributes_to_edit'] = VALIDATION_MESSAGES_ATTRIBUTES[
209
                type_of_check]['attributes_to_edit']
210
        # TODO add checks for other messages
211
        else:
212
            context['attributes_to_show'] = []
213
            context['attributes_to_edit'] = []
214
        context['submission'] = self.submission
215
216
        return context
217
218
219
# a detail view since I need to operate on a submission object
220
# HINT: rename to a more informative name?
221
class EditSubmissionView(MessagesSubmissionMixin, OwnerMixin, ListView):
222
    template_name = "submissions/submission_edit.html"
223
    paginate_by = 10
224
225
    def get_queryset(self):
226
        """Subsetting names relying submission id"""
227
        self.submission = get_object_or_404(
228
            Submission,
229
            pk=self.kwargs['pk'],
230
            owner=self.request.user)
231
232
        # unknown animals should be removed from a submission. They have no
233
        # data in animal table nor sample
234
        return Name.objects.select_related(
235
                "validationresult",
236
                "animal",
237
                "sample").filter(
238
            Q(submission=self.submission) & (
239
                Q(animal__isnull=False) | Q(sample__isnull=False))
240
            ).order_by('id')
241
242 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...
243
        handler = super(EditSubmissionView, self).dispatch(
244
                request, *args, **kwargs)
245
246
        # here I've done get_queryset. Check for submission status
247
        if hasattr(self, "submission") and not self.submission.can_edit():
248
            message = "Cannot edit submission: current status is: %s" % (
249
                    self.submission.get_status_display())
250
251
            logger.warning(message)
252
            messages.warning(
253
                request=self.request,
254
                message=message,
255
                extra_tags="alert alert-dismissible alert-warning")
256
257
            return redirect(self.submission.get_absolute_url())
258
259
        return handler
260
261
    def get_context_data(self, **kwargs):
262
        # Call the base implementation first to get a context
263
        context = super(EditSubmissionView, self).get_context_data(**kwargs)
264
265
        # add submission to context
266
        context["submission"] = self.submission
267
268
        return context
269
270
271
class ListSubmissionsView(OwnerMixin, ListView):
272
    model = Submission
273
    template_name = "submissions/submission_list.html"
274
    ordering = ['-created_at']
275
    paginate_by = 10
276
277
278
class ReloadSubmissionView(OwnerMixin, UpdateView):
279
    form_class = ReloadForm
280
    model = Submission
281
    template_name = 'submissions/submission_reload.html'
282
283
    def form_invalid(self, form):
284
        messages.error(
285
            self.request,
286
            message="Please correct the errors below",
287
            extra_tags="alert alert-dismissible alert-danger")
288
289
        return super(ReloadSubmissionView, self).form_invalid(form)
290
291
    def form_valid(self, form):
292
        self.object = form.save(commit=False)
293
294
        # update object and force status
295
        self.object.message = "waiting for data loading"
296
        self.object.status = WAITING
297
        self.object.save()
298
299
        # HINT: can I change datasource type?
300
301
        # call the proper method
302
        if self.object.datasource_type == CRYOWEB_TYPE:
303
            # a valid submission start a task
304
            res = import_from_cryoweb.delay(self.object.pk)
305
            logger.info(
306
                "Start cryoweb reload process with task %s" % res.task_id)
307
308
        elif self.object.datasource_type == CRB_ANIM_TYPE:
309
            # a valid submission start a task
310
            my_task = ImportCRBAnimTask()
311
312
            # a valid submission start a task
313
            res = my_task.delay(self.object.pk)
314
            logger.info(
315
                "Start crbanim reload process with task %s" % res.task_id)
316
317
        else:
318
            # this is not an invalid form. Template is not implemented
319
            # it is a error in our application
320
            # TODO: call the proper method
321
            message = "{datasource} reload is not implemented".format(
322
                datasource=self.object.get_datasource_type_display())
323
324
            messages.error(
325
                self.request,
326
                message,
327
                extra_tags="alert alert-dismissible alert-danger")
328
329
            # set an error message to this submission
330
            self.object.message = message
331
            self.object.status = ERROR
332
            self.object.save()
333
334
        # a redirect to self.object.get_absolute_url()
335
        return HttpResponseRedirect(self.get_success_url())
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
403
404
def fix_validation(request, pk, type):
405
    # Fetch all required ids from input names and use it as keys
406
    keys_to_fix = dict()
407
    for key_to_fix in request.POST:
408
        if 'to_edit' in key_to_fix:
409
            keys_to_fix[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...
410
                = request.POST[key_to_fix]
411
412
    submission = Submission.objects.get(pk=pk)
413
    submission.message = "waiting for data updating"
414
    submission.status = WAITING
415
    submission.save()
416
417
    # Update validation summary
418
    summary_obj, created = ValidationSummary.objects.get_or_create(
419
        submission=submission, type=type)
420
    summary_obj.submission = submission
421
    summary_obj.pass_count = 0
422
    summary_obj.warning_count = 0
423
    summary_obj.error_count = 0
424
    summary_obj.issues_count = 0
425
    summary_obj.validation_known_count = 0
426
    summary_obj.messages = list()
427
    summary_obj.save()
428
429
    # create a task
430
    if type == 'animal':
431
        my_task = BatchUpdateAnimals()
432
    elif type == 'sample':
433
        my_task = BatchUpdateSamples()
434
435
    # a valid submission start a task
436
    res = my_task.delay(pk, keys_to_fix)
0 ignored issues
show
introduced by
The variable my_task does not seem to be defined for all execution paths.
Loading history...
437
    logger.info(
438
        "Start crbanim importing process with task %s" % res.task_id)
439
    return HttpResponseRedirect(reverse('submissions:detail', args=(pk,)))
440