Repository: ckan/ckan

Issue:
Deleting first Group and Organization custom field is not possible
### CKAN Version if known (or site URL)

2.6+

- https://demo.ckan.org/organization/edit/20171207_mk
- https://demo.ckan.org/group/edit/16848646846
- https://beta.ckan.org/organization/edit/avg-data
- https://beta.ckan.org/group/edit/anotheraillysgroup

### Please describe the expected behaviour

Each **Group or Organization custom field** marked for deletion should be deleted once the Update button is clicked. The deletion button should be displayed as a **red button with an icon**.

### Please describe the actual behaviour

Whey you try to delete the first Group or Organization custom field, it only gets deleted when there are *at least two custom fields set*. When you try to delete the first custom field, when it's the only one in the group with paired values added, it does not get deleted once the Update button is clicked.

Also the **delete icon is not rendered** in < 2.8 and it's displayed as a simple checkbox.

### What steps can be taken to reproduce the issue? 

Navigate to any of the links above and mark the first custom field for deletion, then click Update. The field will not be deleted. Then add values to at least two custom fields, Update and mark the first one for deletion. When you click on Update again, it will be deleted.

Also, note that the delete icon is not rendered and only a simple checkbox is shown for marking.

The custom fields functionality is fully working for datasets, so please check the Manage dataset page for reference. 



Discussion:

Resolving patch:
diff --git a/ckan/lib/dictization/model_save.py b/ckan/lib/dictization/model_save.py
--- a/ckan/lib/dictization/model_save.py
+++ b/ckan/lib/dictization/model_save.py
@@ -133,19 +133,6 @@ def package_extras_save(extra_dicts, obj, context):
         state = 'deleted'
         extra.state = state
 
-def group_extras_save(extras_dicts, context):
-
-    model = context["model"]
-    session = context["session"]
-
-    result_dict = {}
-    for extra_dict in extras_dicts:
-        if extra_dict.get("deleted"):
-            continue
-        result_dict[extra_dict["key"]] = extra_dict["value"]
-
-    return result_dict
-
 def package_tag_list_save(tag_dicts, package, context):
     allow_partial_update = context.get("allow_partial_update", False)
     if tag_dicts is None and allow_partial_update:
@@ -412,14 +399,17 @@ def group_dict_save(group_dict, context, prevent_packages_update=False):
             'Groups: %r  Tags: %r', pkgs_edited, group_users_changed,
             group_groups_changed, group_tags_changed)
 
-    extras = group_extras_save(group_dict.get("extras", {}), context)
-    if extras or not allow_partial_update:
-        old_extras = set(group.extras.keys())
-        new_extras = set(extras.keys())
-        for key in old_extras - new_extras:
+    extras = group_dict.get("extras", [])
+    new_extras = {i['key'] for i in extras}
+    if extras:
+        old_extras = group.extras
+        for key in set(old_extras) - new_extras:
             del group.extras[key]
-        for key in new_extras:
-            group.extras[key] = extras[key]
+        for x in extras:
+            if 'deleted' in x and x['key'] in old_extras:
+                del group.extras[x['key']]
+                continue
+            group.extras[x['key']] = x['value']
 
     # We will get a list of packages that we have either added or
     # removed from the group, and trigger a re-index.


Test patch: