askvortsov1 / dynamic-django-forms

A simple, reusable Django app that allows you to build (and respond to) dynamic forms. Perfect for surveys, position applications, etc.
MIT License
44 stars 12 forks source link

Bug DynamicFormMixin class does not commit ResponseModel Foreign Key properly #3

Closed manojgudi closed 2 years ago

manojgudi commented 3 years ago

Firstly, thanks for creating this package @askvortsov1 , its been v useful! I've been following the example included in the source code, the docker file works well, however in my own implementation which is very similar I get a strange django.db.utils.IntegrityError when I try to save response to a dynamic form. Here's the gist of my models:

from django.db import models

# Create your models here.
from django.db import models
from dynamic_forms.models import FormField, ResponseField
from authentication.models import Organization

class DynamicFormModel(models.Model):
    name         = models.CharField(max_length=100)
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, null=True)
    form         = FormField()

    def __str__(self):
        return "Dynamic Form #{}:{}".format(self.pk, self.name)

class DynamicFormResponseModel(models.Model):
    dynamicForm = models.ForeignKey(DynamicFormModel, on_delete=models.CASCADE)
    response    = ResponseField()

Here's the stack trace

Environment:

Request Method: POST
Request URL: http://127.0.0.1:8000/dynamicforms/dynamic-forms/1/response/

Django Version: 3.1.3
Python Version: 3.6.9
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'dynamicforms',
 'authentication',
 'dynamic_forms',
 'crispy_forms']
Installed Middleware:
('whitenoise.middleware.WhiteNoiseMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware')

Traceback (most recent call last):
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)

The above exception (null value in column "dynamicForm_id" of relation "dynamicforms_dynamicformresponsemodel" violates not-null constraint
DETAIL:  Failing row contains (21, {"Are you a piece of shit<br>": "option-certainly", "My role is ..., null).
) was the direct cause of the following exception:
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/views/generic/edit.py", line 172, in post
    return super().post(request, *args, **kwargs)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/views/generic/edit.py", line 142, in post
    return self.form_valid(form)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/dynamic_forms/views.py", line 31, in form_valid
    action.save()
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/models/base.py", line 754, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/models/base.py", line 792, in save_base
    force_update, using, update_fields,
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/models/base.py", line 895, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/models/base.py", line 935, in _do_insert
    using=using, raw=raw,
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/models/query.py", line 1254, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1397, in execute_sql
    cursor.execute(sql, params)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/johnathon/projects/acs/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)

Exception Type: IntegrityError at /dynamicforms/dynamic-forms/1/response/
Exception Value: null value in column "dynamicForm_id" of relation "dynamicforms_dynamicformresponsemodel" violates not-null constraint
DETAIL:  Failing row contains (21, {"Are you a person<br>": "option-certainly", "My role is ..., null).

I was able to fix this by changing the definition of DynamicFormMixin class, here's the diff:

@@ -16,6 +16,7 @@
         form = super().get_form(*args, **kwargs)
         # Get instance of model containing form used for this response. Save this object as an instance variable for use in form_valid method
         form_instance_pk = self.kwargs[self.form_pk_url_kwarg]
+        self.form_instance_pk = form_instance_pk
         self.form_instance = self._get_object_containing_form(form_instance_pk)
         # Get json form configuration from form-containing object
         json_data = getattr(self.form_instance, self.form_field)
@@ -26,6 +27,7 @@
     def form_valid(self, form):
         action = form.save(commit=False)
         action.survey = self.form_instance
+        setattr(action, self.form_pk_url_kwarg, self.form_instance_pk)
         action.save()
         return super().form_valid(form)

Am I missing something? Is this a bug? If yes, would be happy to help you a PR, let me know, thanks!

askvortsov1 commented 3 years ago

Looks like I hardcoded the response fk field as survey for some reason, and to top it off, all my local usage overrides form_valid so the issue didn't occur for me. Brilliant :see_no_evil:.

I've released a new version, could you check if that works better?

askvortsov1 commented 2 years ago

Closing as should be fixed. Feel free to reopen if you're still running into issues.