File: fields.py

package info (click to toggle)
python-django-import-export 4.3.5-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,300 kB
  • sloc: python: 11,650; makefile: 180; sh: 63; javascript: 50
file content (168 lines) | stat: -rw-r--r-- 5,958 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.fields import NOT_PROVIDED
from django.db.models.manager import Manager

from . import widgets
from .exceptions import FieldError


class Field:
    """
    ``Field`` represents a mapping between an ``instance`` field and a representation of
    the field's data.

    :param attribute: A string of either an instance attribute or callable of
        the instance.

    :param column_name: An optional column name for the column that represents
        this field in the export.

    :param widget: Defines a widget that will be used to represent this
        field's data in the export, or transform the value during import.

    :param readonly: A Boolean which defines if this field will be ignored
        during import.

    :param default: This value will be returned by
        :meth:`~import_export.fields.Field.clean` if this field's widget returned
        a value defined in :attr:`~import_export.fields.empty_values`.

    :param saves_null_values: Controls whether null values are saved on the instance.
      This can be used if the widget returns null, but there is a default instance
      value which should not be overwritten.

    :param dehydrate_method: You can provide a `dehydrate_method` as a string to use
        instead of the default `dehydrate_{field_name}` syntax, or you can provide
        a callable that will be executed with the instance as its argument.

    :param m2m_add: changes save of this field to add the values, if they do not exist,
        to a ManyToMany field instead of setting all values.  Only useful if field is
        a ManyToMany field.
    """

    empty_values = [None, ""]

    def __init__(
        self,
        attribute=None,
        column_name=None,
        widget=None,
        default=NOT_PROVIDED,
        readonly=False,
        saves_null_values=True,
        dehydrate_method=None,
        m2m_add=False,
    ):
        self.attribute = attribute
        self.default = default
        self.column_name = column_name
        if not widget:
            widget = widgets.Widget()
        self.widget = widget
        self.readonly = readonly
        self.saves_null_values = saves_null_values
        self.dehydrate_method = dehydrate_method
        self.m2m_add = m2m_add

    def __repr__(self):
        """
        Displays the module, class and name of the field.
        """
        path = f"{self.__class__.__module__}.{self.__class__.__name__}"
        if self.column_name is not None:
            return f"<{path}: {self.column_name}>"
        return "<%s>" % path

    def clean(self, row, **kwargs):
        """
        Translates the value stored in the imported datasource to an
        appropriate Python object and returns it.
        """
        try:
            value = row[self.column_name]
        except KeyError:
            raise KeyError(
                "Column '%s' not found in dataset. Available "
                "columns are: %s" % (self.column_name, list(row))
            )

        value = self.widget.clean(value, row=row, **kwargs)

        if value in self.empty_values and self.default != NOT_PROVIDED:
            if callable(self.default):
                return self.default()
            return self.default

        return value

    def get_value(self, instance):
        """
        Returns the value of the instance's attribute.
        """

        # The objects of a queryset can be dictionaries if the values method is used.
        if isinstance(instance, dict):
            if self.attribute not in instance:
                return None
            return instance[self.attribute]

        if self.attribute is None:
            return None

        attrs = self.attribute.split("__")
        value = instance

        for attr in attrs:
            try:
                value = getattr(value, attr, None)
            except (ValueError, ObjectDoesNotExist):
                # needs to have a primary key value before a many-to-many
                # relationship can be used.
                return None
            if value is None:
                return None

        # RelatedManager and ManyRelatedManager classes are callable in
        # Django >= 1.7 but we don't want to call them
        if callable(value) and not isinstance(value, Manager):
            value = value()
        return value

    def save(self, instance, row, is_m2m=False, **kwargs):
        """
        If this field is not declared readonly, the instance's attribute will
        be set to the value returned by :meth:`~import_export.fields.Field.clean`.
        """
        if not self.readonly:
            attrs = self.attribute.split("__")
            for attr in attrs[:-1]:
                instance = getattr(instance, attr, None)
            cleaned = self.clean(row, **kwargs)
            if cleaned is not None or self.saves_null_values:
                if not is_m2m:
                    setattr(instance, attrs[-1], cleaned)
                else:
                    if self.m2m_add:
                        getattr(instance, attrs[-1]).add(*cleaned)
                    else:
                        getattr(instance, attrs[-1]).set(cleaned)

    def export(self, instance, **kwargs):
        """
        Returns value from the provided instance converted to export
        representation.
        """
        value = self.get_value(instance)
        return self.widget.render(value, **kwargs)

    def get_dehydrate_method(self, field_name=None):
        """
        Returns method name to be used for dehydration of the field.
        Defaults to `dehydrate_{field_name}`
        """
        DEFAULT_DEHYDRATE_METHOD_PREFIX = "dehydrate_"

        if not self.dehydrate_method and not field_name:
            raise FieldError("Both dehydrate_method and field_name are not supplied.")

        return self.dehydrate_method or DEFAULT_DEHYDRATE_METHOD_PREFIX + field_name