Repository: CiviWiki/OpenCiviWiki

Issue:
Add UI to threads to toggle draft status
Allow users to compose threads in draft status, and then toggle the draft status when ready to publish.


# Task
Add UI elements that:
- [ ] Allow authors to toggle draft mode for their Thread entries
- [ ] Ensure that authors can edit their Thread while in draft mode


Discussion:

Resolving patch:
diff --git a/project/api/write.py b/project/api/write.py
--- a/project/api/write.py
+++ b/project/api/write.py
@@ -36,6 +36,10 @@ def new_thread(request):
     if state:
         new_thread_data['state'] = state
 
+    is_draft = request.POST.get('is_draft', True)
+    if is_draft:
+        new_thread_data['is_draft'] = is_draft
+
     new_t = Thread(**new_thread_data)
     new_t.save()
 
@@ -123,10 +127,13 @@ def rateCivi(request):
     rating = request.POST.get('rating', '')
     account = Account.objects.get(user=request.user)
 
-    c = Civi.objects.get(id=civi_id)
+    voted_civi = Civi.objects.get(id=civi_id)
+
+    if voted_civi.thread.is_draft:
+        return HttpResponseServerError(reason=str("Cannot vote on a civi that is in a thread still in draft mode"))
 
     try:
-        prev_act = Activity.objects.get(civi=c, account=account)
+        prev_act = Activity.objects.get(civi=voted_civi, account=account)
     except Activity.DoesNotExist:
         prev_act = None
 
@@ -135,28 +142,28 @@ def rateCivi(request):
 
         activity_data = {
             'account': account,
-            'thread': c.thread,
-            'civi': c,
+            'thread': voted_civi.thread,
+            'civi': voted_civi,
         }
 
         if rating == "vneg":
-            c.votes_vneg = c.votes_vneg + 1
+            voted_civi.votes_vneg = voted_civi.votes_vneg + 1
             vote_val = 'vote_vneg'
         elif rating == "neg":
-            c.votes_neg = c.votes_neg + 1
+            voted_civi.votes_neg = voted_civi.votes_neg + 1
             vote_val = 'vote_neg'
         elif rating == "neutral":
-            c.votes_neutral = c.votes_neutral + 1
+            voted_civi.votes_neutral = voted_civi.votes_neutral + 1
             vote_val = 'vote_neutral'
         elif rating == "pos":
-            c.votes_pos = c.votes_pos + 1
+            voted_civi.votes_pos = voted_civi.votes_pos + 1
             vote_val = 'vote_pos'
         elif rating == "vpos":
             # c.votes_vpos = c.votes_vpos + 1
             vote_val = 'vote_vpos'
         activity_data['activity_type'] = vote_val
 
-        c.save()
+        voted_civi.save()
 
         if prev_act:
             prev_act.activity_type = vote_val
@@ -254,46 +261,62 @@ def deleteCivi(request):
 
 @login_required
 def editThread(request):
-
     thread_id = request.POST.get('thread_id')
+
+    if not thread_id:
+        return HttpResponseBadRequest(reason="Invalid Thread Reference")
+
+    # Change Publish State to True
+    is_draft = request.POST.get('is_draft', True)
+    if is_draft in ["false", "False"] or not is_draft:
+        draft_thread = Thread.objects.get(id=thread_id)
+        if request.user.id is not draft_thread.author_id:
+            return HttpResponseForbidden("Only the original creator can publish the draft")
+
+        draft_thread.is_draft = False
+
+        try:
+            draft_thread.save()
+        except Exception as e:
+            return HttpResponseServerError(reason=str(e))
+
+        return JsonResponse({'data': "remove from draft"})
+
     title = request.POST.get('title')
     summary = request.POST.get('summary')
     category_id = request.POST.get('category_id')
     level = request.POST.get('level')
     state = request.POST.get('state')
-    if thread_id:
 
-        try:
-            t = Thread.objects.get(id=thread_id)
-            category_id = request.POST.get('category_id')
-            if request.user.username != t.author.user.username:
-                return HttpResponseBadRequest(reason="No Edit Rights")
-
-            t.title = title
-            t.summary = summary
-            t.category_id = category_id
-            t.level = level
-            t.state = state
-            t.save()
-        except Exception as e:
-            return HttpResponseServerError(reason=str(e))
-
-        return_data = {
-            'thread_id': thread_id,
-            'title': t.title,
-            'summary': t.summary,
-            "category": {
-                "id": t.category.id,
-                "name": t.category.name
-            },
-            "level": t.level,
-            "state": t.state if t.level == "state" else "",
-            "location": t.level if not t.state else dict(US_STATES).get(t.state),
-        }
-        return JsonResponse({'data': return_data})
-    else:
-        return HttpResponseBadRequest(reason="Invalid Thread Reference")
+    try:
+        req_edit_thread = Thread.objects.get(id=thread_id)
+
+        category_id = request.POST.get('category_id')
+        if request.user.username != req_edit_thread.author.user.username:
+            return HttpResponseBadRequest("No Edit Rights")
+
+        req_edit_thread.title = title
+        req_edit_thread.summary = summary
+        req_edit_thread.category_id = category_id
+        req_edit_thread.level = level
+        req_edit_thread.state = state
+        req_edit_thread.save()
+    except Exception as e:
+        return HttpResponseServerError(reason=str(e))
 
+    return_data = {
+        'thread_id': thread_id,
+        'title': req_edit_thread.title,
+        'summary': req_edit_thread.summary,
+        "category": {
+            "id": req_edit_thread.category.id,
+            "name": req_edit_thread.category.name
+        },
+        "level": req_edit_thread.level,
+        "state": req_edit_thread.state if req_edit_thread.level == "state" else "",
+        "location": req_edit_thread.level if not req_edit_thread.state else dict(US_STATES).get(req_edit_thread.state),
+    }
+    return JsonResponse({'data': return_data})
 
 
 @login_required
@@ -327,8 +350,10 @@ def editUser(request):
         "latitude": r.get('latitude', account.latitude),
     }
 
+
     try:
-        Account.objects.filter(id=account.id).update(**data)
+        account.__dict__.update(data)
+        account.save()
     except Exception as e:
         return HttpResponseServerError(reason=str(e))
 
diff --git a/project/frontend_views/views.py b/project/frontend_views/views.py
--- a/project/frontend_views/views.py
+++ b/project/frontend_views/views.py
@@ -28,9 +28,9 @@ def base_view(request):
     all_categories = list(Category.objects.values_list('id', flat=True))
     user_categories = list(a.categories.values_list('id', flat=True)) or all_categories
 
-    feed_threads = [Thread.objects.summarize(t) for t in Thread.objects.order_by('-created')]
-    top5_threads = list(Thread.objects.all().order_by('-num_views')[:5].values('id', 'title'))
-
+    feed_threads = [Thread.objects.summarize(t) for t in Thread.objects.exclude(is_draft=True).order_by('-created')]
+    top5_threads = list(Thread.objects.filter(is_draft=False).order_by('-num_views')[:5].values('id', 'title'))
+    my_draft_threads = [Thread.objects.summarize(t) for t in Thread.objects.filter(author_id=a.id).exclude(is_draft=False).order_by('-created')]
 
     states = sorted(US_STATES, key=lambda s: s[1])
     data = {
@@ -38,7 +38,8 @@ def base_view(request):
         'states': states,
         'user_categories': user_categories,
         'threads': feed_threads,
-        'trending': top5_threads
+        'trending': top5_threads,
+        'draft_threads': my_draft_threads
     }
 
     return TemplateResponse(request, 'feed.html', {'data': json.dumps(data)})
@@ -132,6 +133,7 @@ def issue_thread(request, thread_id=None):
 
     data = {
         'thread_id': thread_id,
+        'is_draft': t.is_draft,
         'thread_wiki_data': json.dumps(thread_wiki_data),
         'thread_body_data': json.dumps(thread_body_data)
     }


Test patch: