Repository: dpgmediamagazines/django-arctic

Issue:
Link to admin pages with two or more parameters are not accessible via action_links
Adding an auxiliary link on admin pages via the '`action_links`' variable is broken if an URL has at least two parameters.
The tuple that is added to the "action_links" list accepts only the format "`('name', 'url-name', 'css-class')`" raising a **`NoReverseMatch`** exception in such cases.

The template responsible to render action_list's configuration (`django-arctic/arctic/templates/arctic/partials/base_data_table.html`) only accepts the first parameter, as follows:
`<a href="{% url link.url row.0 %}" class="action-{{ link.label }}" title="{{ link.label|capfirst }}">`


Discussion:

Resolving patch:
diff --git a/arctic/generics.py b/arctic/generics.py
--- a/arctic/generics.py
+++ b/arctic/generics.py
@@ -274,6 +274,7 @@ class ListView(View, base.ListView):
     tool_links = []   # Global links. For Example "Add object"
     prefix = ''  # Prefix for embedding multiple list views in detail view
     max_embeded_list_items = 10  # when displaying a list in a column
+    primary_key = 'pk'
 
     def get(self, request, *args, **kwargs):
         objects = self.get_object_list()
@@ -370,7 +371,7 @@ def _reverse_field_link(self, url, obj):
                 args.append(find_attribute(obj, arg))
         else:
             named_url = url
-            args = [get_attribute(obj, 'pk')]
+            args = [get_attribute(obj, self.primary_key)]
 
         # Instead of giving NoReverseMatch exception
         # its more desirable, for field_links in listviews
@@ -433,6 +434,7 @@ def get_list_header(self):
         return result
 
     def get_list_items(self, objects):
+        self.has_action_links = False
         items = []
         if not self.get_fields():
             for obj in objects:
@@ -444,20 +446,15 @@ def get_list_items(self, objects):
         fields = []
         field_links = self.get_field_links()
         field_classes = self.get_field_classes()
+        field_actions = self.get_action_links()
         for field in self.get_fields():
             fields.append(field[0] if type(field) in (list, tuple)
                           else field)
         for obj in objects:
             row = []
-            # get the row's foreign key
-            row_id = getattr(obj, 'pk', None)
-            if row_id:
-                row.append(row_id)
-            else:
-                # while annotating, it's possible that there is no pk
-                row.append('')
+
             for field_name in fields:
-                field = {'field': field_name}
+                field = {'type': 'field', 'field': field_name}
                 base_field_name = field_name.split('__')[0]
                 field_class = get_field_class(objects, base_field_name)
                 field['value'] = self.get_field_value(field_name, obj)
@@ -478,7 +475,18 @@ def get_list_items(self, objects):
                 if field_name in field_classes:
                     field['class'] = field_classes[field_name]
                 row.append(field)
+            if field_actions:
+                actions = []
+                for field_action in field_actions:
+                    actions.append({'label': field_action['label'],
+                                    'icon': field_action['icon'],
+                                    'url': self._reverse_field_link(
+                                        field_action['url'], obj)})
+                row.append({'type': 'actions', 'actions': actions})
+                self.has_action_links = True
             items.append(row)
+
+            print(items)
         return items
 
     def get_field_value(self, field_name, obj):
@@ -504,9 +512,11 @@ def get_action_links(self):
         else:
             allowed_action_links = []
             for link in self.action_links:
-
+                url = named_url = link[1]
+                if type(url) in (list, tuple):
+                    named_url = url[0]
                 # check permission based on named_url
-                if not view_from_url(link[1]).\
+                if not view_from_url(named_url).\
                         has_permission(self.request.user):
                     continue
 
@@ -514,7 +524,7 @@ def get_action_links(self):
                 if len(link) == 3:  # if an icon class is given
                     icon = link[2]
                 allowed_action_links.append({'label': link[0],
-                                             'url': link[1],
+                                             'url': url,
                                              'icon': icon})
             return allowed_action_links
 
@@ -604,8 +614,9 @@ def get_context_data(self, **kwargs):
         context['prefix'] = self.prefix
         context['list_header'] = self.get_list_header()
         context['list_items'] = self.get_list_items(context['object_list'])
-        context['action_links'] = self.get_action_links()
         context['tool_links'] = self.get_tool_links()
+        # self.has_action_links is set in get_list_items
+        context['has_action_links'] = self.has_action_links
         context['tool_links_icon'] = self.get_tool_links_icon()
         if self.get_filter_fields() or self.get_search_fields():
             context['has_filter'] = True


Test patch:
diff --git a/tests/test_generics/test_list_view.py b/tests/test_generics/test_list_view.py
--- a/tests/test_generics/test_list_view.py
+++ b/tests/test_generics/test_list_view.py
@@ -27,7 +27,9 @@ def test_single_item(self, admin_client):
         article = ArticleFactory()
         response = self._request(admin_client)
         self._assert_list_items_len(response, 1)
-        assert response.context_data['list_items'][0][0] == article.pk
+        field = response.context_data['list_items'][0][0]['field']
+        assert response.context_data['list_items'][0][0]['value'] == \
+            getattr(article, field)
 
     def test_paginated_items(self, admin_client):
         """
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
@@ -28,10 +28,10 @@ def test_virtual_field(self, admin_client):
         self._assert_list_items_len(response, 1)
 
         item = response.context_data['list_items'][0]
-        assert item[1]['value'] == article.title
-        assert item[2]['value'] == article.description
-        assert item[3]['value'] == article.published
-        assert item[4]['value'] == article.category.name
+        assert item[0]['value'] == article.title
+        assert item[1]['value'] == article.description
+        assert item[2]['value'] == article.published
+        assert item[3]['value'] == article.category.name
 
     def test_missing_virtual_field(self, admin_client):
         """
@@ -44,10 +44,7 @@ def test_missing_virtual_field(self, admin_client):
 
         search_virtual_field = False
         for field in response.context_data['list_items'][0]:
-            if type(field) == int:
-                continue
-
-            if 'virtual_field' in field['field']:
+            if field['type'] == 'field' and 'virtual_field' in field['field']:
                 search_virtual_field = True
 
         assert search_virtual_field is False