internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: safe-global/safe-config-service

Issue:
Integrate logging middleware
The service should integrate with a Logging Middleware in order to follow the standard logging structure that https://github.com/gnosis/safe-transaction-service follows.

See Logging Middleware here: https://github.com/gnosis/safe-transaction-service/blob/ed0720ce238153e678d0e6df5d48707c7be99fdd/config/settings/base.py#L91


Discussion:

Resolving patch:
diff --git a/src/config/middleware.py b/src/config/middleware.py
new file mode 100644
--- /dev/null
+++ b/src/config/middleware.py
@@ -0,0 +1,37 @@
+import logging
+import time
+
+from django.http import HttpRequest
+
+
+class LoggingMiddleware:
+    def __init__(self, get_response):
+        self.get_response = get_response
+        self.logger = logging.getLogger("LoggingMiddleware")
+
+    @staticmethod
+    def get_milliseconds_now():
+        return int(time.time() * 1000)
+
+    def __call__(self, request: HttpRequest):
+        # before view (and other middleware) are called
+        milliseconds = self.get_milliseconds_now()
+
+        response = self.get_response(request)
+
+        # after view is called
+        if request.resolver_match:
+            route = (
+                request.resolver_match.route[1:]
+                if request.resolver_match
+                else request.path
+            )
+            self.logger.info(
+                "MT::%s::%s::%s::%d::%s",
+                request.method,
+                route,
+                self.get_milliseconds_now() - milliseconds,
+                response.status_code,
+                request.path,
+            )
+        return response
diff --git a/src/config/settings.py b/src/config/settings.py
--- a/src/config/settings.py
+++ b/src/config/settings.py
@@ -43,6 +43,7 @@
 ]
 
 MIDDLEWARE = [
+    "config.middleware.LoggingMiddleware",
     "django.middleware.security.SecurityMiddleware",
     "django.contrib.sessions.middleware.SessionMiddleware",
     "django.middleware.common.CommonMiddleware",
@@ -52,6 +53,38 @@
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
 ]
 
+LOGGING = {
+    "version": 1,
+    "disable_existing_loggers": False,
+    "formatters": {
+        "short": {"format": "%(asctime)s %(message)s"},
+        "verbose": {
+            "format": "%(asctime)s [%(levelname)s] [%(processName)s] %(message)s"
+        },
+    },
+    "handlers": {
+        "console": {
+            "class": "logging.StreamHandler",
+            "formatter": "verbose",
+        },
+        "console_short": {
+            "class": "logging.StreamHandler",
+            "formatter": "short",
+        },
+    },
+    "root": {
+        "handlers": ["console"],
+        "level": os.getenv("ROOT_LOG_LEVEL", "INFO"),
+    },
+    "loggers": {
+        "LoggingMiddleware": {
+            "handlers": ["console_short"],
+            "level": "INFO",
+            "propagate": False,
+        },
+    },
+}
+
 ROOT_URLCONF = "config.urls"
 
 TEMPLATES = [


Test patch:
Repository: safe-global/safe-config-service

Issue:
Change default renderer to JSONRenderer
- The renderer in production should show the json payload without a browsable API


Discussion:

Resolving patch:
diff --git a/src/config/settings.py b/src/config/settings.py
--- a/src/config/settings.py
+++ b/src/config/settings.py
@@ -27,10 +27,21 @@
 
 # https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-ALLOWED_HOSTS
 allowed_hosts = os.getenv("DJANGO_ALLOWED_HOSTS", ".localhost,127.0.0.1,[::1]")
-ALLOWED_HOSTS = list(map(str.strip, allowed_hosts.split(",")))
+ALLOWED_HOSTS = [allowed_host.strip() for allowed_host in allowed_hosts.split(",")]
 
 # Application definition
 
+default_renderer_classes = os.getenv(
+    "REST_DEFAULT_RENDERER_CLASSES", "rest_framework.renderers.JSONRenderer"
+)
+REST_FRAMEWORK = {
+    # https://www.django-rest-framework.org/api-guide/renderers/
+    "DEFAULT_RENDERER_CLASSES": [
+        default_renderer_class.strip()
+        for default_renderer_class in default_renderer_classes.split(",")
+    ]
+}
+
 INSTALLED_APPS = [
     "safe_apps.apps.AppsConfig",
     "django.contrib.admin",


Test patch:
Repository: safe-global/safe-config-service

Issue:
Add /check endpoint
This endpoint should be used to check if the server is alive and ready to receive requests


Discussion:

Resolving patch:
diff --git a/src/config/urls.py b/src/config/urls.py
--- a/src/config/urls.py
+++ b/src/config/urls.py
@@ -1,7 +1,9 @@
 from django.contrib import admin
+from django.http import HttpResponse
 from django.urls import include, path
 
 urlpatterns = [
     path("api/v1/", include("safe_apps.urls", namespace="v1")),
     path("admin/", admin.site.urls),
+    path("check/", lambda request: HttpResponse("Ok"), name="check"),
 ]


Test patch:
Repository: safe-global/safe-config-service

Issue:
Serve static files with Nginx
When running the application with Nginx as reverse-proxy, static files (such as Admin CSS) are not correctly collected and served


Discussion:

Resolving patch:
diff --git a/src/config/settings.py b/src/config/settings.py
--- a/src/config/settings.py
+++ b/src/config/settings.py
@@ -171,6 +171,8 @@
 
 STATIC_URL = "/static/"
 
+STATIC_ROOT = "staticfiles"
+
 # Default primary key field type
 # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
 


Test patch:
Repository: safe-global/safe-config-service

Issue:
Missing CORS headers
Since CORS headers are missing, resources from this application cannot be accessed by other domains


Discussion:

Resolving patch:
diff --git a/src/config/settings.py b/src/config/settings.py
--- a/src/config/settings.py
+++ b/src/config/settings.py
@@ -39,6 +39,7 @@
 }
 
 INSTALLED_APPS = [
+    "corsheaders",
     "safe_apps.apps.AppsConfig",
     "django.contrib.admin",
     "django.contrib.auth",
@@ -52,6 +53,7 @@
 
 MIDDLEWARE = [
     "config.middleware.LoggingMiddleware",
+    "corsheaders.middleware.CorsMiddleware",
     "django.middleware.security.SecurityMiddleware",
     "django.contrib.sessions.middleware.SessionMiddleware",
     "django.middleware.common.CommonMiddleware",
@@ -182,3 +184,6 @@
 SWAGGER_SETTINGS = {
     "DEFAULT_INFO": "config.swagger_info.SAFE_CONFIG_SERVICE_SWAGGER_INFO"
 }
+
+CORS_ALLOW_ALL_ORIGINS = True
+CORS_URLS_REGEX = r"^/api/.*$"


Test patch:
Repository: safe-global/safe-config-service

Issue:
Provider field is required in Django admin
The `Provider` field of a `SafeApp` is currently required for validation even though its contents can be `null` in the database. This field should be optional on the validation phase.


Discussion:

Resolving patch:
diff --git a/src/safe_apps/migrations/0003_alter_safeapp_provider.py b/src/safe_apps/migrations/0003_alter_safeapp_provider.py
new file mode 100644
--- /dev/null
+++ b/src/safe_apps/migrations/0003_alter_safeapp_provider.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.2.4 on 2021-06-18 12:28
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("safe_apps", "0002_rename_networks_safeapp_chain_ids"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="safeapp",
+            name="provider",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                to="safe_apps.provider",
+            ),
+        ),
+    ]
diff --git a/src/safe_apps/models.py b/src/safe_apps/models.py
--- a/src/safe_apps/models.py
+++ b/src/safe_apps/models.py
@@ -16,7 +16,9 @@ class SafeApp(models.Model):
     icon_url = models.URLField()
     description = models.CharField(max_length=200)
     chain_ids = ArrayField(models.IntegerField())
-    provider = models.ForeignKey(Provider, null=True, on_delete=models.SET_NULL)
+    provider = models.ForeignKey(
+        Provider, null=True, blank=True, on_delete=models.SET_NULL
+    )
 
     def __str__(self):
         return f"{self.name} | {self.url} | chain_ids={self.chain_ids}"


Test patch:
diff --git a/src/safe_apps/tests/test_models.py b/src/safe_apps/tests/test_models.py
--- a/src/safe_apps/tests/test_models.py
+++ b/src/safe_apps/tests/test_models.py
@@ -16,3 +16,10 @@ def test_str_method_outputs_name_url_chain_id(self):
             str(safe_app),
             f"{safe_app.name} | {safe_app.url} | chain_ids={safe_app.chain_ids}",
         )
+
+    @staticmethod
+    def test_empty_provider_validation():
+        safe_app = SafeAppFactory.create(provider=None)
+
+        # Run validations including blank checks
+        safe_app.full_clean()
Repository: safe-global/safe-config-service

Issue:
Use PositiveBigIntegerField for chain_id in safe-apps



Discussion:

Resolving patch:
diff --git a/src/safe_apps/migrations/0006_safeapp_chain_ids_big_int.py b/src/safe_apps/migrations/0006_safeapp_chain_ids_big_int.py
new file mode 100644
--- /dev/null
+++ b/src/safe_apps/migrations/0006_safeapp_chain_ids_big_int.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.2.4 on 2021-07-02 12:42
+
+import django.contrib.postgres.fields
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("safe_apps", "0005_safeapp_visible"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="safeapp",
+            name="chain_ids",
+            field=django.contrib.postgres.fields.ArrayField(
+                base_field=models.PositiveBigIntegerField(), size=None
+            ),
+        ),
+    ]
diff --git a/src/safe_apps/models.py b/src/safe_apps/models.py
--- a/src/safe_apps/models.py
+++ b/src/safe_apps/models.py
@@ -19,7 +19,7 @@ class SafeApp(models.Model):
     name = models.CharField(max_length=200)
     icon_url = models.URLField()
     description = models.CharField(max_length=200)
-    chain_ids = ArrayField(models.IntegerField())
+    chain_ids = ArrayField(models.PositiveBigIntegerField())
     provider = models.ForeignKey(
         Provider, null=True, blank=True, on_delete=models.SET_NULL
     )


Test patch:
Repository: safe-global/safe-config-service

Issue:
Increase max limit for the chains endpoint



Discussion:

Resolving patch:
diff --git a/src/chains/views.py b/src/chains/views.py
--- a/src/chains/views.py
+++ b/src/chains/views.py
@@ -9,7 +9,7 @@
 class ChainsListView(ListAPIView):
     serializer_class = ChainSerializer
     pagination_class = LimitOffsetPagination
-    pagination_class.max_limit = 10
+    pagination_class.max_limit = 100
     pagination_class.default_limit = 10
     queryset = Chain.objects.all()
     filter_backends = [filters.OrderingFilter]


Test patch:
diff --git a/src/chains/tests/test_views.py b/src/chains/tests/test_views.py
--- a/src/chains/tests/test_views.py
+++ b/src/chains/tests/test_views.py
@@ -73,22 +73,22 @@ def test_pagination_next_page(self):
         self.assertEqual(len(response.data["results"]), 10)
 
     def test_request_more_than_max_limit_should_return_max_limit(self):
-        ChainFactory.create_batch(11)
+        ChainFactory.create_batch(101)
         # requesting limit > max_limit
-        url = reverse("v1:chains:list") + f'{"?limit=11"}'
+        url = reverse("v1:chains:list") + f'{"?limit=101"}'
 
         response = self.client.get(path=url, data=None, format="json")
 
         self.assertEqual(response.status_code, 200)
         # number of items should be equal to the number of total items
-        self.assertEqual(response.data["count"], 11)
+        self.assertEqual(response.data["count"], 101)
         self.assertEqual(
             response.data["next"],
-            "http://testserver/api/v1/chains/?limit=10&offset=10",
+            "http://testserver/api/v1/chains/?limit=100&offset=100",
         )
         self.assertEqual(response.data["previous"], None)
         # returned items should still be equal to max_limit
-        self.assertEqual(len(response.data["results"]), 10)
+        self.assertEqual(len(response.data["results"]), 100)
 
     def test_offset_greater_than_count(self):
         ChainFactory.create_batch(11)
Repository: safe-global/safe-config-service

Issue:
Relative path for `static` django resources
@Uxio0 addressed this in this PR: https://github.com/gnosis/safe-transaction-service/pull/400

This would facilitate having a custom mounting point for the services in nginx, making `static`'s location relative.


Discussion:

Resolving patch:
diff --git a/src/config/settings.py b/src/config/settings.py
--- a/src/config/settings.py
+++ b/src/config/settings.py
@@ -109,6 +109,7 @@
 }
 
 ROOT_URLCONF = "config.urls"
+FORCE_SCRIPT_NAME = os.getenv("FORCE_SCRIPT_NAME", default=None)
 
 TEMPLATES = [
     {
@@ -176,8 +177,7 @@
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/3.2/howto/static-files/
 
-STATIC_URL = "/static/"
-
+STATIC_URL = "static/"
 STATIC_ROOT = "staticfiles"
 
 # Default primary key field type


Test patch:
Repository: safe-global/safe-config-service

Issue:
Gas price editable from the Chain model
Please make the Gas Price editable directly via Chain configs.
Like we did for Features and Wallets.
Thanks!


Discussion:

Resolving patch:
diff --git a/src/chains/admin.py b/src/chains/admin.py
--- a/src/chains/admin.py
+++ b/src/chains/admin.py
@@ -4,6 +4,12 @@
 from .models import Chain, Feature, GasPrice, Wallet
 
 
+class GasPriceInline(admin.TabularInline[Model]):
+    model = GasPrice
+    extra = 0
+    verbose_name_plural = "Gas prices set for this chain"
+
+
 class FeatureInline(admin.TabularInline[Model]):
     model = Feature.chains.through
     extra = 0
@@ -30,7 +36,7 @@ class ChainAdmin(admin.ModelAdmin[Chain]):
         "relevance",
         "name",
     )
-    inlines = [FeatureInline, WalletInline]
+    inlines = [FeatureInline, GasPriceInline, WalletInline]
 
 
 @admin.register(GasPrice)


Test patch:
Repository: safe-global/safe-config-service

Issue:
Enforce 2FA for the admin interface
We should enforce 2fa for all accounts that have access to the admin interface.


Discussion:

Resolving patch:
diff --git a/src/config/admin.py b/src/config/admin.py
new file mode 100644
--- /dev/null
+++ b/src/config/admin.py
@@ -0,0 +1,5 @@
+from django.contrib.admin.apps import AdminConfig
+
+
+class OTPAdminConfig(AdminConfig):
+    default_site = "django_otp.admin.OTPAdminSite"
diff --git a/src/config/settings.py b/src/config/settings.py
--- a/src/config/settings.py
+++ b/src/config/settings.py
@@ -49,7 +49,6 @@
     "about.apps.AboutAppConfig",
     "chains.apps.AppsConfig",
     "safe_apps.apps.AppsConfig",
-    "django.contrib.admin",
     "django.contrib.auth",
     "django.contrib.contenttypes",
     "django.contrib.sessions",
@@ -57,6 +56,9 @@
     "django.contrib.staticfiles",
     "rest_framework",
     "drf_yasg",
+    "django_otp",
+    "django_otp.plugins.otp_totp",
+    "django_otp.plugins.otp_static",
 ]
 
 MIDDLEWARE = [
@@ -67,10 +69,19 @@
     "django.middleware.common.CommonMiddleware",
     "django.middleware.csrf.CsrfViewMiddleware",
     "django.contrib.auth.middleware.AuthenticationMiddleware",
+    "django_otp.middleware.OTPMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
 ]
 
+DJANGO_OTP_ADMIN = strtobool(os.getenv("DJANGO_OTP_ADMIN", "true"))
+if DJANGO_OTP_ADMIN:
+    # Use OTP admin
+    INSTALLED_APPS.append("config.admin.OTPAdminConfig")
+else:
+    # Use Default admin
+    INSTALLED_APPS.append("django.contrib.admin")
+
 CACHES = {
     "default": {
         "BACKEND": "django.core.cache.backends.locmem.LocMemCache",


Test patch:
Repository: safe-global/safe-config-service

Issue:
500 Error on unsanitized URL query params 
**Describe the bug**
Error response with 500 Internal server Error is returned to the clients when a unsanitized URL query param is sent to the service.

**To Reproduce**
Steps to reproduce the behavior:
- Check: https://safe-config.safe.global/api/v1/safe-apps/?url=%00

**Expected behavior**
URL input is sanitized beforehand.

**Environment**
 - Staging & production
 - All chains



Discussion:

Resolving patch:
diff --git a/src/safe_apps/views.py b/src/safe_apps/views.py
--- a/src/safe_apps/views.py
+++ b/src/safe_apps/views.py
@@ -59,13 +59,13 @@ def get_queryset(self) -> QuerySet[SafeApp]:
             queryset = queryset.filter(chain_ids__contains=[chain_id])
 
         client_url = self.request.query_params.get("clientUrl")
-        if client_url:
+        if client_url and "\0" not in client_url:
             queryset = queryset.filter(
                 Q(exclusive_clients__url=client_url) | Q(exclusive_clients__isnull=True)
             )
 
         url = self.request.query_params.get("url")
-        if url:
+        if url and "\0" not in url:
             queryset = queryset.filter(url=url)
 
         return queryset


Test patch:
diff --git a/src/safe_apps/tests/test_views.py b/src/safe_apps/tests/test_views.py
--- a/src/safe_apps/tests/test_views.py
+++ b/src/safe_apps/tests/test_views.py
@@ -267,6 +267,16 @@ def test_apps_returned_on_non_existent_client_url(self) -> None:
         self.assertEqual(response.status_code, 200)
         self.assertCountEqual(response.json(), json_response)
 
+    def test_apps_returned_on_null_client_url(self) -> None:
+        url = reverse("v1:safe-apps:list") + "?clientUrl=\0"
+        response = self.client.get(path=url, data=None, format="json")
+        self.assertEqual(response.status_code, 200)
+
+    def test_apps_returned_on_null_url(self) -> None:
+        url = reverse("v1:safe-apps:list") + "?url=\0"
+        response = self.client.get(path=url, data=None, format="json")
+        self.assertEqual(response.status_code, 200)
+
     def test_apps_returned_on_empty_client_url(self) -> None:
         client_1 = ClientFactory.create(url="safe.com")
         safe_app_1 = SafeAppFactory.create()
Repository: safe-global/safe-config-service

Issue:
Bad logo URL when creating a new Safe App
**Describe the bug**
When inserting a new Safe App, `None` is added to the logo image URL instead of the `app_id`. 

Re-uploading the image for the Safe App solves the problem.

**To Reproduce**
Steps to reproduce the behavior:
- Create a new Safe App.
- Check the path for the logo image is not correct (it includes `None` as ID).

**Expected behavior**
A correct Safe App `app_id` is added to the logo path instead of `None`.

**Environment (please complete the following information):**
 - Staging and production.



Discussion:

Resolving patch:
diff --git a/src/safe_apps/models.py b/src/safe_apps/models.py
--- a/src/safe_apps/models.py
+++ b/src/safe_apps/models.py
@@ -1,4 +1,5 @@
 import os
+import uuid
 from enum import Enum
 from typing import IO, Union
 
@@ -17,7 +18,7 @@
 
 def safe_app_icon_path(instance: "SafeApp", filename: str) -> str:
     _, file_extension = os.path.splitext(filename)
-    return f"safe_apps/{instance.app_id}/icon{file_extension}"
+    return f"safe_apps/{uuid.uuid4()}/icon{file_extension}"
 
 
 def validate_safe_app_icon_size(image: Union[str, IO[bytes]]) -> None:


Test patch:
diff --git a/src/safe_apps/tests/test_models.py b/src/safe_apps/tests/test_models.py
--- a/src/safe_apps/tests/test_models.py
+++ b/src/safe_apps/tests/test_models.py
@@ -16,9 +16,14 @@ class IconTestCase(TestCase):
     def test_icon_upload_path(self) -> None:
         safe_app = SafeAppFactory.create()
 
-        self.assertEqual(
-            safe_app.icon_url.url, f"/media/safe_apps/{safe_app.app_id}/icon.jpg"
+        path_regex = "|".join(
+            [
+                r"\/media\/safe_apps\/",
+                r"[0-9(a-f|A-F)]{8}-[0-9(a-f|A-F)]{4}-4[0-9(a-f|A-F)]{3}-[89ab][0-9(a-f|A-F)]{3}-[0-9(a-f|A-F)]{12}",
+                r"\/icon.jpg",
+            ]
         )
+        self.assertRegex(safe_app.icon_url.url, path_regex)
 
     def test_icon_max_size_validation(self) -> None:
         safe_app = SafeAppFactory.create(
Repository: CiviWiki/OpenCiviWiki

Issue:
 Fix CSRF middleware
There is a [TODO in `settings.py` to 'fix the CSRF middleware'](https://github.com/CiviWiki/OpenCiviWiki/blob/dev/civiwiki/settings.py#L60). This issue is a placeholder to make sure we resolve the issue with CSRF middleware.

What is the issue that prevents us from using CSRF?


Discussion:

Resolving patch:
diff --git a/civiwiki/settings.py b/civiwiki/settings.py
--- a/civiwiki/settings.py
+++ b/civiwiki/settings.py
@@ -57,7 +57,7 @@ def get_env_variable(environment_variable, optional=False):
 MIDDLEWARE_CLASSES = (
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
-    # 'django.middleware.csrf.CsrfViewMiddleware', TODO: fix eventually
+    'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
@@ -66,6 +66,8 @@ def get_env_variable(environment_variable, optional=False):
 )
 
 
+CSRF_USE_SESSIONS = True # Store the CSRF token in the users session instead of in a cookie
+
 CORS_ORIGIN_ALLOW_ALL = True
 ROOT_URLCONF = 'civiwiki.urls'
 LOGIN_URL = '/login'


Test patch:
Repository: CiviWiki/OpenCiviWiki

Issue:
Add 'draft stage' for Threads
Allow **threads** to be created as drafts. The **thread** should have an edit mode and preview.

# Goal
Give early adopters a basic tool to contribute content to CiviWiki. Allow authors to draft/edit their threads before publishing.

# Task
- [x] Add a field/metadata that allows a Thread to be designated as being a 'draft'


Discussion:

Resolving patch:
diff --git a/project/api/migrations/0024_remove_civi_links.py b/project/api/migrations/0024_remove_civi_links.py
new file mode 100644
--- /dev/null
+++ b/project/api/migrations/0024_remove_civi_links.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0023_auto_20170615_0827'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='civi',
+            name='links',
+        ),
+    ]
diff --git a/project/api/migrations/0025_thread_is_draft.py b/project/api/migrations/0025_thread_is_draft.py
new file mode 100644
--- /dev/null
+++ b/project/api/migrations/0025_thread_is_draft.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0024_remove_civi_links'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='thread',
+            name='is_draft',
+            field=models.BooleanField(default=True),
+        ),
+    ]
diff --git a/project/api/models/thread.py b/project/api/models/thread.py
--- a/project/api/models/thread.py
+++ b/project/api/models/thread.py
@@ -86,6 +86,9 @@ def _get_image_url(self): #TODO: move this to utils
     created = models.DateTimeField(auto_now_add=True, blank=True, null=True)
     last_modified = models.DateTimeField(auto_now=True, blank=True, null=True)
 
+    # Allow draft stage threads (default to True)
+    is_draft = models.BooleanField(default=True)
+
     num_views = models.IntegerField(default=0)
     num_civis = models.IntegerField(default=0)
     num_solutions = models.IntegerField(default=0)


Test patch: