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

submissions.views   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 459
Duplicated Lines 7.84 %

Importance

Changes 0
Metric Value
wmc 43
eloc 287
dl 36
loc 459
rs 8.96
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
B MessagesSubmissionMixin.get_context_data() 0 26 5
A CreateSubmissionView.form_invalid() 0 7 1
A DetailSubmissionView.get_context_data() 0 15 1
A CreateSubmissionView.form_valid() 0 47 3
A SubmissionValidationSummaryView.get_context_data() 0 21 4
A DeleteSubmissionView.delete() 0 16 1
A EditSubmissionView.dispatch() 18 18 3
A EditSubmissionView.get_context_data() 0 8 1
A DeleteSubmissionView.get_context_data() 0 21 3
B FixValidation.post() 0 38 5
B SubmissionValidationSummaryFixErrorsView.get_queryset() 0 23 5
A SubmissionValidationSummaryFixErrorsView.get_context_data() 0 21 3
A ReloadSubmissionView.form_valid() 0 37 3
A ReloadSubmissionView.form_invalid() 0 7 1
A DeleteSubmissionView.dispatch() 18 18 3
A EditSubmissionView.get_queryset() 0 16 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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