Repository: django-import-export/django-import-export

Issue:
Pass row values to every Widget
AFAIU it will help create widgets with autocreation of related ForeignKey instances that require two fields already present in the row. For example:
- FK to `Person` with `first_name`, `last_name`
- FK to `Quote` that requires a `Book` (let's suppose it's identifiable just by name) and that book is already in the import fields
- FK to `POI` that is identifiable with two (or even three) coordinates



Discussion:
Related: #188 

I too have come across the same issue!

Related Issue #378

I am thinking about adding optional **kwargs to widgets methods. Beside row value, current format could be added.


Resolving patch:
diff --git a/import_export/fields.py b/import_export/fields.py
--- a/import_export/fields.py
+++ b/import_export/fields.py
@@ -63,7 +63,7 @@ def clean(self, data):
                                                 list(data.keys())))
 
         try:
-            value = self.widget.clean(value)
+            value = self.widget.clean(value, row=data)
         except ValueError as e:
             raise ValueError("Column '%s': %s" % (self.column_name, e))
 
@@ -119,4 +119,4 @@ def export(self, obj):
         value = self.get_value(obj)
         if value is None:
             return ""
-        return self.widget.render(value)
+        return self.widget.render(value, obj)
diff --git a/import_export/widgets.py b/import_export/widgets.py
--- a/import_export/widgets.py
+++ b/import_export/widgets.py
@@ -21,8 +21,7 @@ class Widget(object):
     :meth:`~import_export.widgets.Widget.clean` and
     :meth:`~import_export.widgets.Widget.render`.
     """
-
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         """
         Returns an appropriate Python object for an imported value.
 
@@ -35,7 +34,7 @@ def clean(self, value):
         """
         return value
 
-    def render(self, value):
+    def render(self, value, obj=None):
         """
         Returns an export representation of a Python value.
 
@@ -54,7 +53,7 @@ def is_empty(self, value):
         # 0 is not empty
         return value is None or value == ""
 
-    def render(self, value):
+    def render(self, value, obj=None):
         return value
 
 
@@ -63,7 +62,7 @@ class FloatWidget(NumberWidget):
     Widget for converting floats fields.
     """
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if self.is_empty(value):
             return None
         return float(value)
@@ -74,7 +73,7 @@ class IntegerWidget(NumberWidget):
     Widget for converting integer fields.
     """
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if self.is_empty(value):
             return None
         return int(float(value))
@@ -85,7 +84,7 @@ class DecimalWidget(NumberWidget):
     Widget for converting decimal fields.
     """
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if self.is_empty(value):
             return None
         return Decimal(value)
@@ -96,7 +95,7 @@ class CharWidget(Widget):
     Widget for converting text fields.
     """
 
-    def render(self, value):
+    def render(self, value, obj=None):
         return force_text(value)
 
 
@@ -107,12 +106,12 @@ class BooleanWidget(Widget):
     TRUE_VALUES = ["1", 1]
     FALSE_VALUE = "0"
 
-    def render(self, value):
+    def render(self, value, obj=None):
         if value is None:
             return ""
         return self.TRUE_VALUES[0] if value else self.FALSE_VALUE
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if value == "":
             return None
         return True if value in self.TRUE_VALUES else False
@@ -135,7 +134,7 @@ def __init__(self, format=None):
             formats = (format,)
         self.formats = formats
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if not value:
             return None
         for format in self.formats:
@@ -145,7 +144,7 @@ def clean(self, value):
                 continue
         raise ValueError("Enter a valid date.")
 
-    def render(self, value):
+    def render(self, value, obj=None):
         if not value:
             return ""
         try:
@@ -172,7 +171,7 @@ def __init__(self, format=None):
             formats = (format,)
         self.formats = formats
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if not value:
             return None
         for format in self.formats:
@@ -188,7 +187,7 @@ def clean(self, value):
                 continue
         raise ValueError("Enter a valid date/time.")
 
-    def render(self, value):
+    def render(self, value, obj=None):
         if not value:
             return ""
         return value.strftime(self.formats[0])
@@ -211,7 +210,7 @@ def __init__(self, format=None):
             formats = (format,)
         self.formats = formats
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if not value:
             return None
         for format in self.formats:
@@ -221,7 +220,7 @@ def clean(self, value):
                 continue
         raise ValueError("Enter a valid time.")
 
-    def render(self, value):
+    def render(self, value, obj=None):
         if not value:
             return ""
         return value.strftime(self.formats[0])
@@ -267,11 +266,37 @@ def __init__(self, model, field='pk', *args, **kwargs):
         self.field = field
         super(ForeignKeyWidget, self).__init__(*args, **kwargs)
 
-    def clean(self, value):
+    def get_queryset(self, value, row, *args, **kwargs):
+        """
+        Returns a queryset of all objects for this Model.
+
+        Overwrite this method if you want to limit the pool of objects from
+        which the related object is retrieved.
+
+        :param value: The field's value in the datasource.
+        :param row: The datasource's current row.
+
+        As an example; if you'd like to have ForeignKeyWidget look up a Person
+        by their pre- **and** lastname column, you could subclass the widget
+        like so::
+
+            class FullNameForeignKeyWidget(ForeignKeyWidget):
+                def get_queryset(self, value, row):
+                    return self.model.objects.filter(
+                        first_name__iexact=row["first_name"],
+                        last_name__iexact=row["last_name"]
+                    )
+        """
+        return self.model.objects.all()
+
+    def clean(self, value, row=None, *args, **kwargs):
         val = super(ForeignKeyWidget, self).clean(value)
-        return self.model.objects.get(**{self.field: val}) if val else None
+        if val:
+            return self.get_queryset(value, row, *args, **kwargs).get(**{self.field: val})
+        else:
+            return None
 
-    def render(self, value):
+    def render(self, value, obj=None):
         if value is None:
             return ""
         return getattr(value, self.field)
@@ -297,7 +322,7 @@ def __init__(self, model, separator=None, field=None, *args, **kwargs):
         self.field = field
         super(ManyToManyWidget, self).__init__(*args, **kwargs)
 
-    def clean(self, value):
+    def clean(self, value, row=None, *args, **kwargs):
         if not value:
             return self.model.objects.none()
         if isinstance(value, float):
@@ -309,6 +334,6 @@ def clean(self, value):
             '%s__in' % self.field: ids
         })
 
-    def render(self, value):
+    def render(self, value, obj=None):
         ids = [smart_text(getattr(obj, self.field)) for obj in value.all()]
         return self.separator.join(ids)


Test patch:
diff --git a/tests/core/tests/widgets_tests.py b/tests/core/tests/widgets_tests.py
--- a/tests/core/tests/widgets_tests.py
+++ b/tests/core/tests/widgets_tests.py
@@ -156,6 +156,19 @@ def test_render(self):
     def test_render_empty(self):
         self.assertEqual(self.widget.render(None), "")
 
+    def test_clean_multi_column(self):
+        class BirthdayWidget(widgets.ForeignKeyWidget):
+            def get_queryset(self, value, row):
+                return self.model.objects.filter(
+                    birthday=row['birthday']
+                )
+        author2 = Author.objects.create(name='Foo')
+        author2.birthday = "2016-01-01"
+        author2.save()
+        birthday_widget = BirthdayWidget(Author, 'name')
+        row = {'name': "Foo", 'birthday': author2.birthday}
+        self.assertEqual(birthday_widget.clean("Foo", row), author2)
+
 
 class ManyToManyWidget(TestCase):