Repository: dpgmediamagazines/django-arctic

Issue:
Confusing exception in virtual fields
At the moment(at least in Arctic 0.9.6) if you set a field that doesn't exist AttributeError appears with an error saying that given field doesn't exist. However, the interesting thing happens in following case:
* add a virtual field, let's say with name `any_name`
* create a method `get_any_name_field`
* in the code of the field do any mistaken call that will cause AttributeError(for instance, try to get missing property from Django settings)
* AttributeError with message "Field "any_name" doesn't exist" appears, but it exists and the problem is underlying code fails(settings has no attribute or whatever)

It sounds like not a bit deal, but I faced this issue few times and guys from SchoolBank thought virtual field doesn't work at all because of this error :D



Discussion:

Resolving patch:
diff --git a/arctic/generics.py b/arctic/generics.py
--- a/arctic/generics.py
+++ b/arctic/generics.py
@@ -488,21 +488,21 @@ def get_list_items(self, objects):
         return items
 
     def get_field_value(self, field_name, obj):
-        try:  # first try to find a virtual field
-            virtual_field_name = "get_{}_field".format(field_name)
+        # first try to find a virtual field
+        virtual_field_name = "get_{}_field".format(field_name)
+        if hasattr(self, virtual_field_name):
             return getattr(self, virtual_field_name)(obj)
-        except AttributeError:  # then try get_{field}_display
-            try:
-                # Get the choice display value
-                parent_objs = '__'.join(
-                    field_name.split('__')[:-1])
-                method_name = '{}__get_{}_display'.format(
-                    parent_objs,
-                    field_name.split('__')[-1]).strip('__')
-                return find_attribute(obj, method_name)()
-            except (AttributeError, TypeError):
-                # finally get field's value
-                return find_attribute(obj, field_name)
+        try:
+            # Get the choice display value
+            parent_objs = '__'.join(
+                field_name.split('__')[:-1])
+            method_name = '{}__get_{}_display'.format(
+                parent_objs,
+                field_name.split('__')[-1]).strip('__')
+            return find_attribute(obj, method_name)()
+        except (AttributeError, TypeError):
+            # finally get field's value
+            return find_attribute(obj, field_name)
 
     def get_action_links(self):
         if not self.action_links:


Test patch:
diff --git a/tests/test_virtual_fields.py b/tests/test_virtual_fields.py
--- a/tests/test_virtual_fields.py
+++ b/tests/test_virtual_fields.py
@@ -48,3 +48,33 @@ def test_missing_virtual_field(self, admin_client):
                 search_virtual_field = True
 
         assert search_virtual_field is False
+
+    def test_missing_virtual_field_error(self, admin_client):
+        """
+        Error happens on wrong virtual field name
+        """
+        article = ArticleFactory()
+        view = ArticleListView()
+        view.fields = ['title', 'description', 'published', 'virtual_field']
+
+        exc = pytest.raises(
+            AttributeError, view.get_field_value, 'virtual_field', article)
+
+        assert str(exc.value) == \
+            "'Article' object has no attribute 'virtual_field'", \
+            exc.value.message
+
+    def test_missing_virtual_field_execution_attribute_error(self,
+                                                             admin_client):
+        """
+        Error happens on wrong virtual field name
+        """
+        article = ArticleFactory()
+        view = ArticleListView()
+        view.fields = ['title', 'description', 'published', 'broken']
+        view.get_broken_field = lambda obj: obj.unknown_field
+        exc = pytest.raises(
+            AttributeError, view.get_field_value, 'broken', article)
+        assert str(exc.value) == \
+            "'Article' object has no attribute 'unknown_field'", \
+            exc.value.message