@@ -307,7 +307,7 @@ class FormDefinitionAdmin(admin.ModelAdmin):
307307 inlines = [FormFieldInline , WorkflowDefinitionInline ]
308308 filter_horizontal = ("submit_groups" , "view_groups" , "admin_groups" )
309309 change_form_template = "admin/django_forms_workflows/formdef_change_form.html"
310- actions = ["clone_forms" , "diff_forms" , "export_as_json" , "push_forms_to_remote" ]
310+ actions = ["clone_forms" , "diff_forms" , "export_as_json" ]
311311
312312 fieldsets = (
313313 (
@@ -550,6 +550,20 @@ def clone_forms(self, request, queryset):
550550 group = sag .group ,
551551 position = sag .position ,
552552 )
553+ # Clone SubWorkflowDefinition if present
554+ try :
555+ swc = wf .sub_workflow_config
556+ SubWorkflowDefinition .objects .create (
557+ parent_workflow = cloned_wf ,
558+ sub_workflow = swc .sub_workflow ,
559+ count_field = swc .count_field ,
560+ section_label = swc .section_label ,
561+ label_template = swc .label_template ,
562+ trigger = swc .trigger ,
563+ data_prefix = swc .data_prefix ,
564+ )
565+ except SubWorkflowDefinition .DoesNotExist :
566+ pass
553567
554568 cloned_count += 1
555569 except Exception as e :
@@ -654,21 +668,19 @@ def sync_import_admin_view(self, request):
654668
655669 # ── Push/Pull admin actions & views ───────────────────────────────────────
656670
657- @admin .action (description = "Push selected forms to a remote instance" )
658- def push_forms_to_remote (self , request , queryset ):
659- """Admin action: redirect to the push view with selected form PKs."""
660- pks = "," .join (str (pk ) for pk in queryset .values_list ("pk" , flat = True ))
661- push_url = reverse ("admin:formdefinition_sync_push" )
662- return HttpResponseRedirect (f"{ push_url } ?pks={ pks } " )
663-
664671 def sync_pull_admin_view (self , request ):
665672 """Multi-step admin page for pulling form definitions from a remote instance.
666673
667674 Step 0 (GET) – show remote picker (configured remotes + manual URL/token)
668675 Step 1 (POST) – fetch available forms from the remote, show checkbox list
669676 Step 2 (POST) – import selected forms, show results
670677 """
671- from .sync_api import fetch_remote_payload , get_sync_remotes , import_payload
678+ from .sync_api import (
679+ build_export_payload ,
680+ fetch_remote_payload ,
681+ get_sync_remotes ,
682+ import_payload ,
683+ )
672684
673685 context = dict (self .admin_site .each_context (request ))
674686 context ["title" ] = "Pull Forms from Remote"
@@ -719,11 +731,46 @@ def sync_pull_admin_view(self, request):
719731 )
720732
721733 remote_forms = payload .get ("forms" , [])
734+
735+ # Build per-form diffs: remote vs local
736+ import json
737+
738+ from .diff_views import _build_summary
739+
740+ form_diffs = []
741+ for rf in remote_forms :
742+ slug = rf .get ("form" , {}).get ("slug" , "" )
743+ local_fd = FormDefinition .objects .filter (slug = slug ).first ()
744+ if local_fd :
745+ local_payload = build_export_payload (
746+ FormDefinition .objects .filter (pk = local_fd .pk )
747+ )
748+ local_data = (
749+ local_payload ["forms" ][0 ]
750+ if local_payload .get ("forms" )
751+ else {}
752+ )
753+ local_json = json .dumps (local_data , indent = 2 , default = str )
754+ remote_json = json .dumps (rf , indent = 2 , default = str )
755+ summary = _build_summary ([local_data , rf ])
756+ form_diffs .append (
757+ {
758+ "slug" : slug ,
759+ "local_json" : local_json ,
760+ "remote_json" : remote_json ,
761+ "summary" : summary [0 ] if summary else None ,
762+ "is_new" : False ,
763+ }
764+ )
765+ else :
766+ form_diffs .append ({"slug" : slug , "is_new" : True })
767+
722768 context ["step" ] = 1
723769 context ["remote_url" ] = remote_url
724770 context ["remote_token" ] = remote_token
725771 context ["remote_name" ] = remote_name
726772 context ["remote_forms" ] = remote_forms
773+ context ["form_diffs" ] = form_diffs
727774 return render (
728775 request , "admin/django_forms_workflows/sync_pull.html" , context
729776 )
@@ -776,39 +823,31 @@ def sync_pull_admin_view(self, request):
776823 return render (request , "admin/django_forms_workflows/sync_pull.html" , context )
777824
778825 def sync_push_admin_view (self , request ):
779- """Admin page for pushing local form definitions to a remote instance .
826+ """Multi-step admin page for pushing form definitions to a remote.
780827
781- Arrives with ``?pks=1,2,3`` (selected from the changelist action) or
782- without PKs (push all forms).
783-
784- Step 0 (GET) – show forms to be pushed + remote picker
785- Step 1 (POST) – execute push, show results
828+ Mirrors the pull flow:
829+ Step 0 (GET) – show remote picker (configured remotes + manual URL/token)
830+ Step 1 (POST) – show all local forms with checkboxes + diff status
831+ Step 2 (POST) – execute push for selected forms, show results
786832 """
787- from .sync_api import get_sync_remotes , push_to_remote
833+ from .sync_api import (
834+ build_export_payload ,
835+ fetch_remote_payload ,
836+ get_sync_remotes ,
837+ push_to_remote ,
838+ )
788839
789840 context = dict (self .admin_site .each_context (request ))
790841 context ["title" ] = "Push Forms to Remote"
791842 context ["opts" ] = self .model ._meta
792843 context ["remotes" ] = get_sync_remotes ()
793844 context ["step" ] = 0
794845
795- # Resolve form queryset from ?pks= query param or POST field
796- pks_raw = request .GET .get ("pks" ) or request .POST .get ("pks" , "" )
797- if pks_raw :
798- try :
799- pk_list = [int (p ) for p in pks_raw .split ("," ) if p .strip ().isdigit ()]
800- except ValueError :
801- pk_list = []
802- queryset = self .model .objects .filter (pk__in = pk_list )
803- else :
804- queryset = self .model .objects .all ()
805-
806- context ["forms_to_push" ] = queryset
807- context ["pks" ] = pks_raw
808-
809846 if request .method == "POST" :
810- step = request .POST .get ("step" , "push" )
811- if step == "push" :
847+ step = request .POST .get ("step" , "1" )
848+
849+ # ── Step 1: show local forms with diff status ────────────────
850+ if step == "1" :
812851 remote_idx = request .POST .get ("remote_idx" , "" )
813852 manual_url = request .POST .get ("manual_url" , "" ).strip ()
814853 manual_token = request .POST .get ("manual_token" , "" ).strip ()
@@ -836,24 +875,95 @@ def sync_push_admin_view(self, request):
836875 "Please select a configured remote or enter a URL and token."
837876 )
838877 return render (
839- request , "admin/django_forms_workflows/sync_push.html" , context
878+ request ,
879+ "admin/django_forms_workflows/sync_push.html" ,
880+ context ,
840881 )
841882
883+ # Build per-form diffs: local vs remote
884+
885+ from .diff_views import _build_summary
886+
887+ local_qs = self .model .objects .all ()
888+ local_payload = build_export_payload (local_qs )
889+ local_forms = local_payload .get ("forms" , [])
890+
891+ try :
892+ remote_payload = fetch_remote_payload (remote_url , remote_token )
893+ remote_forms_list = remote_payload .get ("forms" , [])
894+ except Exception :
895+ remote_forms_list = []
896+
897+ remote_by_slug = {
898+ f .get ("form" , {}).get ("slug" ): f for f in remote_forms_list
899+ }
900+ form_diffs = []
901+ for lf in local_forms :
902+ slug = lf .get ("form" , {}).get ("slug" , "" )
903+ name = lf .get ("form" , {}).get ("name" , slug )
904+ rf = remote_by_slug .get (slug )
905+ if rf :
906+ summary = _build_summary ([rf , lf ])
907+ form_diffs .append (
908+ {
909+ "slug" : slug ,
910+ "name" : name ,
911+ "summary" : summary [0 ] if summary else None ,
912+ "is_new" : False ,
913+ }
914+ )
915+ else :
916+ form_diffs .append ({"slug" : slug , "name" : name , "is_new" : True })
917+
918+ context ["step" ] = 1
919+ context ["remote_url" ] = remote_url
920+ context ["remote_token" ] = remote_token
921+ context ["remote_name" ] = remote_name
922+ context ["conflict" ] = conflict
923+ context ["local_forms" ] = local_forms
924+ context ["form_diffs" ] = form_diffs
925+ return render (
926+ request ,
927+ "admin/django_forms_workflows/sync_push.html" ,
928+ context ,
929+ )
930+
931+ # ── Step 2: push selected forms ──────────────────────────────
932+ if step == "2" :
933+ remote_url = request .POST .get ("remote_url" , "" ).strip ()
934+ remote_token = request .POST .get ("remote_token" , "" ).strip ()
935+ remote_name = request .POST .get ("remote_name" , remote_url )
936+ conflict = request .POST .get ("conflict" , "update" )
937+ selected_slugs = request .POST .getlist ("slugs" )
938+
939+ if not selected_slugs :
940+ context ["error" ] = "No forms selected."
941+ return render (
942+ request ,
943+ "admin/django_forms_workflows/sync_push.html" ,
944+ context ,
945+ )
946+
947+ queryset = self .model .objects .filter (slug__in = selected_slugs )
842948 try :
843949 result = push_to_remote (
844950 remote_url , remote_token , queryset , conflict = conflict
845951 )
846952 except Exception as exc :
847953 context ["error" ] = f"Push failed: { exc } "
848954 return render (
849- request , "admin/django_forms_workflows/sync_push.html" , context
955+ request ,
956+ "admin/django_forms_workflows/sync_push.html" ,
957+ context ,
850958 )
851959
852- context ["step" ] = 1
960+ context ["step" ] = 2
853961 context ["remote_name" ] = remote_name
854962 context ["push_result" ] = result
855963 return render (
856- request , "admin/django_forms_workflows/sync_push.html" , context
964+ request ,
965+ "admin/django_forms_workflows/sync_push.html" ,
966+ context ,
857967 )
858968
859969 return render (request , "admin/django_forms_workflows/sync_push.html" , context )
0 commit comments