Passed
Push — master ( ed1559...680383 )
by Swen
01:54
created

LocalizedField.deconstruct()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 10
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
1 1
from django.conf import settings
2 1
from django.contrib.postgres.fields import HStoreField
3 1
from django.db.utils import IntegrityError
4
5 1
from ..forms import LocalizedFieldForm
6 1
from .localized_value import LocalizedValue
7
8
9 1
class LocalizedField(HStoreField):
10
    """A field that has the same value in multiple languages.
11
12
    Internally this is stored as a :see:HStoreField where there
13
    is a key for every language."""
14
15 1
    Meta = None
16
17 1
    def __init__(self, *args, uniqueness=None, **kwargs):
18
        """Initializes a new instance of :see:LocalizedValue."""
19
20 1
        super(LocalizedField, self).__init__(*args, **kwargs)
21
22 1
        self.uniqueness = uniqueness
23
24 1
    @staticmethod
25
    def from_db_value(value, *_):
26
        """Turns the specified database value into its Python
27
        equivalent.
28
29
        Arguments:
30
            value:
31
                The value that is stored in the database and
32
                needs to be converted to its Python equivalent.
33
34
        Returns:
35
            A :see:LocalizedValue instance containing the
36
            data extracted from the database.
37
        """
38
39 1
        if not value:
40 1
            return LocalizedValue()
41
42 1
        return LocalizedValue(value)
43
44 1
    def to_python(self, value: dict) -> LocalizedValue:
45
        """Turns the specified database value into its Python
46
        equivalent.
47
48
        Arguments:
49
            value:
50
                The value that is stored in the database and
51
                needs to be converted to its Python equivalent.
52
53
        Returns:
54
            A :see:LocalizedValue instance containing the
55
            data extracted from the database.
56
        """
57
58 1
        if not value or not isinstance(value, dict):
59 1
            return LocalizedValue()
60
61 1
        return LocalizedValue(value)
62
63 1
    def get_prep_value(self, value: LocalizedValue) -> dict:
64
        """Turns the specified value into something the database
65
        can store.
66
67
        If an illegal value (non-LocalizedValue instance) is
68
        specified, we'll treat it as an empty :see:LocalizedValue
69
        instance, on which the validation will fail.
70
71
        Arguments:
72
            value:
73
                The :see:LocalizedValue instance to serialize
74
                into a data type that the database can understand.
75
76
        Returns:
77
            A dictionary containing a key for every language,
78
            extracted from the specified value.
79
        """
80
81
        # default to None if this is an unknown type
82 1
        if not isinstance(value, LocalizedValue) and value:
83 1
            value = None
84
85 1
        if value:
86 1
            cleaned_value = self.clean(value)
87 1
            self.validate(cleaned_value)
88
        else:
89 1
            cleaned_value = value
90
91 1
        return super(LocalizedField, self).get_prep_value(
92
            cleaned_value.__dict__ if cleaned_value else None
93
        )
94
95 1
    def clean(self, value, *_):
96
        """Cleans the specified value into something we
97
        can store in the database.
98
99
        For example, when all the language fields are
100
        left empty, and the field is allows to be null,
101
        we will store None instead of empty keys.
102
103
        Arguments:
104
            value:
105
                The value to clean.
106
107
        Returns:
108
            The cleaned value, ready for database storage.
109
        """
110
111 1
        if not value or not isinstance(value, LocalizedValue):
112 1
            return None
113
114
        # are any of the language fiels None/empty?
115 1
        is_all_null = True
116 1
        for lang_code, _ in settings.LANGUAGES:
117 1
            if value.get(lang_code):
118 1
                is_all_null = False
119 1
                break
120
121
        # all fields have been left empty and we support
122
        # null values, let's return null to represent that
123 1
        if is_all_null and self.null:
124 1
            return None
125
126 1
        return value
127
128 1
    def validate(self, value: LocalizedValue, *_):
129
        """Validates that the value for the primary language
130
        has been filled in.
131
132
        Exceptions are raises in order to notify the user
133
        of invalid values.
134
135
        Arguments:
136
            value:
137
                The value to validate.
138
        """
139
140 1
        if self.null:
141 1
            return
142
143 1
        primary_lang_val = getattr(value, settings.LANGUAGE_CODE)
144
145 1
        if not primary_lang_val:
146 1
            raise IntegrityError(
147
                'null value in column "%s.%s" violates not-null constraint' % (
148
                    self.name,
149
                    settings.LANGUAGE_CODE
150
                )
151
            )
152
153 1
    def formfield(self, **kwargs):
154
        """Gets the form field associated with this field."""
155
156 1
        defaults = {
157
            'form_class': LocalizedFieldForm
158
        }
159
160 1
        defaults.update(kwargs)
161 1
        return super().formfield(**defaults)
162
163 1
    def deconstruct(self):
164
        """Gets the values to pass to :see:__init__ when
165
        re-creating this object."""
166
167 1
        values = super(LocalizedField, self).deconstruct()
168 1
        values[3].update({
169
            'uniqueness': self.uniqueness
170
        })
171
172
        return values
173