Passed
Pull Request — master (#31)
by Swen
01:24
created

LocalizedField.deconstruct()   A

Complexity

Conditions 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 2
c 3
b 0
f 1
dl 0
loc 11
ccs 5
cts 5
cp 1
crap 2
rs 9.4285
1 1
import json
2
3 1
from typing import Union
0 ignored issues
show
Configuration introduced by
The import typing could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
4
5 1
from django.conf import settings
0 ignored issues
show
Configuration introduced by
The import django.conf could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
6 1
from django.db.utils import IntegrityError
0 ignored issues
show
Configuration introduced by
The import django.db.utils could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
7
8 1
from psqlextra.fields import HStoreField
0 ignored issues
show
Configuration introduced by
The import psqlextra.fields could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
9
10 1
from ..forms import LocalizedFieldForm
11 1
from ..value import LocalizedValue
12 1
from ..descriptor import LocalizedValueDescriptor
13
14
15 1
class LocalizedField(HStoreField):
16
    """A field that has the same value in multiple languages.
17
18
    Internally this is stored as a :see:HStoreField where there
19
    is a key for every language."""
20
21 1
    Meta = None
22
23
    # The class to wrap instance attributes in. Accessing to field attribute in
24
    # model instance will always return an instance of attr_class.
25 1
    attr_class = LocalizedValue
26
27
    # The descriptor to use for accessing the attribute off of the class.
28 1
    descriptor_class = LocalizedValueDescriptor
29
30 1
    def __init__(self, *args, **kwargs):
31
        """Initializes a new instance of :see:LocalizedField."""
32
33 1
        super(LocalizedField, self).__init__(*args, **kwargs)
34
35 1
        if self.required is None and self.blank:
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named blank.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
Bug introduced by
The member required does not seem to be defined here; its definition is on line 36.
Loading history...
36
            self.required = []
37 1
        elif self.required is None and not self.blank:
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named blank.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
38 1
            self.required = [settings.LANGUAGE_CODE]
39
        elif self.required is True:
40
            self.required = [lang_code for lang_code, _ in settings.LANGUAGES]
41
42 1
    def contribute_to_class(self, model, name, **kwargs):
43
        """Adds this field to the specifed model.
44
45
        Arguments:
46
            cls:
47
                The model to add the field to.
48
49
            name:
50
                The name of the field to add.
51
        """
52 1
        super(LocalizedField, self).contribute_to_class(model, name, **kwargs)
53 1
        setattr(model, self.name, self.descriptor_class(self))
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named name.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
54
55 1
    @classmethod
56
    def from_db_value(cls, value, *_):
57
        """Turns the specified database value into its Python
58
        equivalent.
59
60
        Arguments:
61
            value:
62
                The value that is stored in the database and
63
                needs to be converted to its Python equivalent.
64
65
        Returns:
66
            A :see:LocalizedValue instance containing the
67
            data extracted from the database.
68
        """
69
70 1
        if not value:
71 1
            if getattr(settings, 'LOCALIZED_FIELDS_EXPERIMENTAL', False):
72 1
                return None
73
            else:
74 1
                return cls.attr_class()
75
76
        # we can get a list if an aggregation expression was used..
77
        # if we the expression was flattened when only one key was selected
78
        # then we don't wrap each value in a localized value, otherwise we do
79 1
        if isinstance(value, list):
80 1
            result = []
81 1
            for inner_val in value:
82 1
                if isinstance(inner_val, dict):
83
                    if inner_val is None:
84
                        result.append(None)
85
                    else:
86
                        result.append(cls.attr_class(inner_val))
87
                else:
88 1
                    result.append(inner_val)
89
90 1
            return result
91
92
        # this is for when you select an individual key, it will be string,
93
        # not a dictionary, we'll give it to you as a flat value, not as a
94
        # localized value instance
95 1
        if not isinstance(value, dict):
96 1
            return value
97
98 1
        return cls.attr_class(value)
99
100 1
    def to_python(self, value: Union[dict, str, None]) -> LocalizedValue:
101
        """Turns the specified database value into its Python
102
        equivalent.
103
104
        Arguments:
105
            value:
106
                The value that is stored in the database and
107
                needs to be converted to its Python equivalent.
108
109
        Returns:
110
            A :see:LocalizedValue instance containing the
111
            data extracted from the database.
112
        """
113
114
        # first let the base class  handle the deserialization, this is in case we
115
        # get specified a json string representing a dict
116 1
        try:
117 1
            deserialized_value = super(LocalizedField, self).to_python(value)
118 1
        except json.JSONDecodeError:
0 ignored issues
show
Bug introduced by
The Module json does not seem to have a member named JSONDecodeError.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
119 1
            deserialized_value = value
120
121 1
        if not deserialized_value:
122 1
            return self.attr_class()
123
124 1
        return self.attr_class(deserialized_value)
125
126 1
    def get_prep_value(self, value: LocalizedValue) -> dict:
127
        """Turns the specified value into something the database
128
        can store.
129
130
        If an illegal value (non-LocalizedValue instance) is
131
        specified, we'll treat it as an empty :see:LocalizedValue
132
        instance, on which the validation will fail.
133
134
        Arguments:
135
            value:
136
                The :see:LocalizedValue instance to serialize
137
                into a data type that the database can understand.
138
139
        Returns:
140
            A dictionary containing a key for every language,
141
            extracted from the specified value.
142
        """
143
144
        # default to None if this is an unknown type
145 1
        if not isinstance(value, LocalizedValue) and value:
146 1
            value = None
147
148 1
        if value:
149 1
            cleaned_value = self.clean(value)
150 1
            self.validate(cleaned_value)
151
        else:
152 1
            cleaned_value = value
153
154 1
        return super(LocalizedField, self).get_prep_value(
155
            cleaned_value.__dict__ if cleaned_value else None
156
        )
157
158 1
    def clean(self, value, *_):
159
        """Cleans the specified value into something we
160
        can store in the database.
161
162
        For example, when all the language fields are
163
        left empty, and the field is allows to be null,
164
        we will store None instead of empty keys.
165
166
        Arguments:
167
            value:
168
                The value to clean.
169
170
        Returns:
171
            The cleaned value, ready for database storage.
172
        """
173
174 1
        if not value or not isinstance(value, LocalizedValue):
175 1
            return None
176
177
        # are any of the language fiels None/empty?
178 1
        is_all_null = True
179 1
        for lang_code, _ in settings.LANGUAGES:
180 1
            if value.get(lang_code) is not None:
181 1
                is_all_null = False
182 1
                break
183
184
        # all fields have been left empty and we support
185
        # null values, let's return null to represent that
186 1
        if is_all_null and self.null:
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named null.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
187 1
            return None
188
189 1
        return value
190
191 1
    def validate(self, value: LocalizedValue, *_):
192
        """Validates that the values has been filled in for all required
193
        languages
194
195
        Exceptions are raises in order to notify the user
196
        of invalid values.
197
198
        Arguments:
199
            value:
200
                The value to validate.
201
        """
202
203 1
        if self.null:
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named null.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
204 1
            return
205
206 1
        for lang in self.required:
207 1
            lang_val = getattr(value, settings.LANGUAGE_CODE)
208
209 1
            if lang_val is None:
210 1
                raise IntegrityError('null value in column "%s.%s" violates '
211
                                     'not-null constraint' % (self.name, lang))
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named name.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
212
213 1
    def formfield(self, **kwargs):
214
        """Gets the form field associated with this field."""
215
216 1
        defaults = dict(form_class=LocalizedFieldForm)
217
218 1
        form_class = kwargs.get('form_class', LocalizedFieldForm)
219 1
        if issubclass(form_class, LocalizedFieldForm):
220 1
            defaults.update(dict(required_langs=self.required))
221
222 1
        defaults.update(kwargs)
223
        return super().formfield(**defaults)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
224