Completed
Push — master ( 8a2447...e2760f )
by Paolo
06:42
created

submissions.forms.UpdateSubmissionForm.clean()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""
4
Created on Tue Jul 24 15:51:05 2018
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
import magic
10
import tempfile
11
12
from django import forms
13
from django.conf import settings
14
15
from common.constants import CRB_ANIM_TYPE, TEMPLATE_TYPE
16
from common.forms import RequestFormMixin
17
from common.helpers import get_admin_emails
18
from uid.models import Submission
19
from crbanim.helpers import CRBAnimReader
20
from excel.helpers import ExcelTemplateReader
21
22
23
class UniqueSubmissionMixin():
24
    def check_submission_exists(self):
25
        """Test if I already have a submission with the same data"""
26
27
        # get unique attributes
28
        unique_together = Submission._meta.unique_together[0]
29
30
        # get submitted attributes
31
        data = {key: self.cleaned_data.get(key) for key in unique_together
32
                if self.cleaned_data.get(key) is not None}
33
34
        # ovverride owner attribute
35
        data['owner'] = self.request.user
36
37
        # test for a submission object with the same attributes
38
        if Submission.objects.filter(**data).exists():
39
            msg = (
40
                "Error: There is already a submission with the same "
41
                "attributes. Please change one of the following: "
42
                "Gene bank name, Gene bank country, Data source type and "
43
                "Data source version")
44
45
            # raising an exception:
46
            raise forms.ValidationError(msg, code='invalid')
47
48
49
class SubmissionFormMixin(UniqueSubmissionMixin):
50
    def clean(self):
51
        # test if I have a submission with the provided data
52
        self.check_submission_exists()
53
54
        # I can call this method without providing a 'uploaded file'
55
        # (for instance, when omitting uploaded file)
56
        if "uploaded_file" in self.cleaned_data:
57
            # avoid file type for excel types (is not an text file)
58
            if ("datasource_type" in self.cleaned_data and
59
                    self.cleaned_data["datasource_type"] != TEMPLATE_TYPE):
60
                self.check_file_encoding()
61
62
            # check crbanim files only if provided
63
            if ("datasource_type" in self.cleaned_data and
64
                    self.cleaned_data["datasource_type"] == CRB_ANIM_TYPE):
65
                self.check_crbanim_columns()
66
67
            # check template files only if provided
68
            if ("datasource_type" in self.cleaned_data and
69
                    self.cleaned_data["datasource_type"] == TEMPLATE_TYPE):
70
                self.check_template_file()
71
72
    def check_file_encoding(self):
73
        uploaded_file = self.cleaned_data['uploaded_file']
74
75
        # read one chunk of such file
76
        chunk = next(uploaded_file.chunks())
77
        magic_line = magic.from_buffer(chunk)
78
        file_type = magic_line.split(",")[0]
79
80
        if "UTF-8" not in file_type and "ASCII" not in file_type:
81
            # create message and add error
82
            msg = (
83
                "Error: file not in UTF-8 nor ASCII format: "
84
                "format was %s" % file_type)
85
86
            # raising an exception:
87
            raise forms.ValidationError(msg, code='invalid')
88
89
    def check_crbanim_columns(self):
90
        """Check if a CRBanim file has mandatory columns"""
91
92
        uploaded_file = self.cleaned_data['uploaded_file']
93
94
        # read one chunk of such file
95
        chunk = next(uploaded_file.chunks())
96
97
        # now determine if CRBanim file is valid. chunk is in binary format
98
        # neet to convert to a string, fortunately I've already check that
99
        # file is in UTF-8
100
        check, not_found = CRBAnimReader.is_valid(chunk.decode("utf-8"))
101
102
        if check is False:
103
            msg = "Error: file lacks of CRBanim mandatory columns: %s" % (
104
                not_found)
105
106
            # raising an exception:
107
            raise forms.ValidationError(msg, code='invalid')
108
109
    def check_template_file(self):
110
        """Check if template file has columns and sheets"""
111
112
        uploaded_file = self.cleaned_data['uploaded_file']
113
114
        chunk = next(uploaded_file.chunks())
115
        magic_line = magic.from_buffer(chunk)
116
117
        if 'Microsoft' not in magic_line:
118
            msg = "The file you provided is not a Template file"
119
            raise forms.ValidationError(msg, code='invalid')
120
121
        # xlrd can manage only files. Write a temporary file
122
        with tempfile.NamedTemporaryFile(delete=True) as tmpfile:
123
            for chunk in uploaded_file.chunks():
124
                tmpfile.write(chunk)
125
126
            # open the file with proper model
127
            reader = ExcelTemplateReader()
128
            reader.read_file(tmpfile.name)
129
130
            # check that template has at least breed, animal, sample sheets
131
            check, not_found = reader.check_sheets()
132
133
            if check is False:
134
                msg = "Error: file lacks of Template mandatory sheets: %s" % (
135
                    not_found)
136
137
                # raising an exception:
138
                raise forms.ValidationError(msg, code='invalid')
139
140
            # check that template has at least breed, animal, sample sheets
141
            check, not_found = reader.check_columns()
142
143
            if check is False:
144
                msg = "Error: file lacks of Template mandatory columns: %s" % (
145
                    not_found)
146
147
                # raising an exception:
148
                raise forms.ValidationError(msg, code='invalid')
149
150
151
class SubmissionForm(SubmissionFormMixin, RequestFormMixin, forms.ModelForm):
152
    class Meta:
153
        model = Submission
154
        fields = (
155
            'title',
156
            'description',
157
            'gene_bank_name',
158
            'gene_bank_country',
159
            'organization',
160
            'datasource_type',
161
            'datasource_version',
162
            'uploaded_file'
163
        )
164
165
        help_texts = {
166
            'uploaded_file': 'Need to be in UTF-8 or ASCII format',
167
            'organization': (
168
                """Who owns the data. Not listed? please """
169
                """<a href="mailto:{0}?subject=please add my organization">"""
170
                """contact us</a>""".format(get_admin_emails()[0])
171
            ),
172
            'datasource_type': (
173
                """example: CryoWeb. Need an empty template file? """
174
                """download it from <a href="%s%s">here</a>""" % (
175
                    settings.MEDIA_URL,
176
                    "Image_sample_empty_template_20191002.xlsx")
177
            )
178
        }
179
180
181
# I use forms.Form since I need to pass primary key as a field,
182
# and I can't use it with a modelform
183
class ReloadForm(SubmissionFormMixin, RequestFormMixin, forms.ModelForm):
184
    # custom attributes
185
    agree_reload = forms.BooleanField(
186
        label="That's fine. Replace my submission data with this file",
187
        help_text="You have to check this box to reload your data")
188
189
    class Meta:
190
        model = Submission
191
        fields = (
192
            'datasource_type',
193
            'datasource_version',
194
            'uploaded_file',
195
        )
196
197
        help_texts = {
198
            'uploaded_file': 'Need to be in UTF-8 or ASCII format',
199
            'datasource_type': (
200
                """example: CryoWeb. Need an empty template file? """
201
                """download it from <a href="%s%s">here</a>""" % (
202
                    settings.MEDIA_URL,
203
                    "Image_sample_empty_template_20191002.xlsx")
204
            )
205
        }
206
207
208
class UpdateSubmissionForm(
209
        UniqueSubmissionMixin, RequestFormMixin, forms.ModelForm):
210
    class Meta:
211
        model = Submission
212
        fields = (
213
            'title',
214
            'description',
215
            'gene_bank_name',
216
            'gene_bank_country',
217
            'organization',
218
            "datasource_type",
219
            "datasource_version",
220
        )
221
222
        help_texts = {
223
            'organization': (
224
                """Who owns the data. Not listed? please """
225
                """<a href="mailto:{0}?subject=please add my organization">"""
226
                """contact us</a>""".format(get_admin_emails()[0])
227
            )
228
        }
229
230
    def clean(self):
231
        # test if I have a submission with the provided data
232
        self.check_submission_exists()
233