Passed
Push — master ( bf9013...8b08e5 )
by Swen
02:02
created

LocalizedField.contribute_to_class()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
ccs 3
cts 3
cp 1
crap 1
1 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...
2 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...
3 1
from django.utils import six, translation
0 ignored issues
show
Configuration introduced by
The import django.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...
4
5 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...
6
7 1
from ..forms import LocalizedFieldForm
8 1
from ..localized_value import LocalizedValue
9
10
11 1
class LocalizedValueDescriptor(object):
12
    """
13
    The descriptor for the localized value attribute on the model instance.
14
    Returns a :see:LocalizedValue when accessed so you can do stuff like::
15
16
        >>> from myapp.models import MyModel
17
        >>> instance = MyModel()
18
        >>> instance.value.en = 'English value'
19
20
    Assigns a strings to active language key in :see:LocalizedValue on
21
    assignment so you can do::
22
23
        >>> from django.utils import translation
24
        >>> from myapp.models import MyModel
25
26
        >>> translation.activate('nl')
27
        >>> instance = MyModel()
28
        >>> instance.title = 'dutch title'
29
        >>> print(instance.title.nl) # prints 'dutch title'
30
    """
31 1
    def __init__(self, field):
32 1
        self.field = field
33
34 1
    def __get__(self, instance, cls=None):
35 1
        if instance is None:
36
            return self
37
38
        # This is slightly complicated, so worth an explanation.
39
        # `instance.localizedvalue` needs to ultimately return some instance of
40
        # `LocalizedValue`, probably a subclass.
41
42
        # The instance dict contains whatever was originally assigned
43
        # in __set__.
44 1
        if self.field.name in instance.__dict__:
45 1
            value = instance.__dict__[self.field.name]
46
        elif instance.pk is not None:
47
            instance.refresh_from_db(fields=[self.field.name])
48
            value = getattr(instance, self.field.name)
49
        else:
50
            value = None
51
52 1
        if value is None:
53 1
            attr = self.field.attr_class()
54 1
            instance.__dict__[self.field.name] = attr
55
56 1
        if isinstance(value, dict):
57 1
            attr = self.field.attr_class(value)
58 1
            instance.__dict__[self.field.name] = attr
59
60 1
        return instance.__dict__[self.field.name]
61
62 1
    def __set__(self, instance, value):
63 1
        if isinstance(value, six.string_types):
64
            self.__get__(instance).set(translation.get_language() or
0 ignored issues
show
Bug introduced by
The Instance of LocalizedValueDescriptor does not seem to have a member named set.

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...
65
                                       settings.LANGUAGE_CODE, value)
66
        else:
67 1
            instance.__dict__[self.field.name] = value
68
69
70 1
class LocalizedField(HStoreField):
71
    """A field that has the same value in multiple languages.
72
73
    Internally this is stored as a :see:HStoreField where there
74
    is a key for every language."""
75
76 1
    Meta = None
77
78
    # The class to wrap instance attributes in. Accessing to field attribute in
79
    # model instance will always return an instance of attr_class.
80 1
    attr_class = LocalizedValue
81
82
    # The descriptor to use for accessing the attribute off of the class.
83 1
    descriptor_class = LocalizedValueDescriptor
84
85 1
    def __init__(self, *args, **kwargs):
86
        """Initializes a new instance of :see:LocalizedField."""
87
88 1
        super(LocalizedField, self).__init__(*args, **kwargs)
89
90 1
    def contribute_to_class(self, cls, name, **kwargs):
91 1
        super(LocalizedField, self).contribute_to_class(cls, name, **kwargs)
92 1
        setattr(cls, 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...
93
94 1
    @classmethod
95
    def from_db_value(cls, value, *_):
96
        """Turns the specified database value into its Python
97
        equivalent.
98
99
        Arguments:
100
            value:
101
                The value that is stored in the database and
102
                needs to be converted to its Python equivalent.
103
104
        Returns:
105
            A :see:LocalizedValue instance containing the
106
            data extracted from the database.
107
        """
108
109 1
        if not value:
110 1
            if getattr(settings, 'LOCALIZED_FIELDS_EXPERIMENTAL', False):
111 1
                return None
112
            else:
113 1
                return cls.attr_class()
114
115 1
        return cls.attr_class(value)
116
117 1
    def to_python(self, value: dict) -> LocalizedValue:
118
        """Turns the specified database value into its Python
119
        equivalent.
120
121
        Arguments:
122
            value:
123
                The value that is stored in the database and
124
                needs to be converted to its Python equivalent.
125
126
        Returns:
127
            A :see:LocalizedValue instance containing the
128
            data extracted from the database.
129
        """
130
131 1
        if not value or not isinstance(value, dict):
132 1
            return self.attr_class()
133
134 1
        return self.attr_class(value)
135
136 1
    def get_prep_value(self, value: LocalizedValue) -> dict:
137
        """Turns the specified value into something the database
138
        can store.
139
140
        If an illegal value (non-LocalizedValue instance) is
141
        specified, we'll treat it as an empty :see:LocalizedValue
142
        instance, on which the validation will fail.
143
144
        Arguments:
145
            value:
146
                The :see:LocalizedValue instance to serialize
147
                into a data type that the database can understand.
148
149
        Returns:
150
            A dictionary containing a key for every language,
151
            extracted from the specified value.
152
        """
153
154
        # default to None if this is an unknown type
155 1
        if not isinstance(value, LocalizedValue) and value:
156 1
            value = None
157
158 1
        if value:
159 1
            cleaned_value = self.clean(value)
160 1
            self.validate(cleaned_value)
161
        else:
162 1
            cleaned_value = value
163
164 1
        return super(LocalizedField, self).get_prep_value(
165
            cleaned_value.__dict__ if cleaned_value else None
166
        )
167
168 1
    def clean(self, value, *_):
169
        """Cleans the specified value into something we
170
        can store in the database.
171
172
        For example, when all the language fields are
173
        left empty, and the field is allows to be null,
174
        we will store None instead of empty keys.
175
176
        Arguments:
177
            value:
178
                The value to clean.
179
180
        Returns:
181
            The cleaned value, ready for database storage.
182
        """
183
184 1
        if not value or not isinstance(value, LocalizedValue):
185 1
            return None
186
187
        # are any of the language fiels None/empty?
188 1
        is_all_null = True
189 1
        for lang_code, _ in settings.LANGUAGES:
190 1
            if value.get(lang_code):
191 1
                is_all_null = False
192 1
                break
193
194
        # all fields have been left empty and we support
195
        # null values, let's return null to represent that
196 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...
197 1
            return None
198
199 1
        return value
200
201 1
    def validate(self, value: LocalizedValue, *_):
202
        """Validates that the value for the primary language
203
        has been filled in.
204
205
        Exceptions are raises in order to notify the user
206
        of invalid values.
207
208
        Arguments:
209
            value:
210
                The value to validate.
211
        """
212
213 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...
214 1
            return
215
216 1
        primary_lang_val = getattr(value, settings.LANGUAGE_CODE)
217
218 1
        if not primary_lang_val:
219 1
            raise IntegrityError(
220
                'null value in column "%s.%s" violates not-null constraint' % (
221
                    self.name,
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...
222
                    settings.LANGUAGE_CODE
223
                )
224
            )
225
226 1
    def formfield(self, **kwargs):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
227
        """Gets the form field associated with this field."""
228
229 1
        defaults = {
230
            'form_class': LocalizedFieldForm
231
        }
232
233 1
        defaults.update(kwargs)
234 1
        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...
235
236 1
    def deconstruct(self):
237
        """Gets the values to pass to :see:__init__ when
238
        re-creating this object."""
239
240 1
        name, path, args, kwargs = super(
241
            LocalizedField, self).deconstruct()
242
243 1
        if self.uniqueness:
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named uniqueness.

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...
244 1
            kwargs['uniqueness'] = self.uniqueness
0 ignored issues
show
Bug introduced by
The Instance of LocalizedField does not seem to have a member named uniqueness.

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...
245
246
        return name, path, args, kwargs
247