I am trying to program an API with Django Rest Framework and Psycopg2 library. This API works with PostgreSQL database. I need to store 2 float array fields in this db because the API works with 2 sensors which stream data to an Android App, and they are storing in the APP in two arrays and they are sent by JSON to the API REST.
These are my models.py, serializers.py and views_api.py:
Models.py
class DataTest(models.Model):
patient = models.ForeignKey(Patient)
label = models.CharField(max_length=36)
angle_data = ArrayField(models.FloatField())
emg_data = ArrayField(models.FloatField())
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.patient
Serializers.py
class DataTestSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
label = serializers.CharField()
angle_data = serializers.ListField(child=serializers.FloatField())
emg_data = serializers.ListField(child=serializers.FloatField())
patient = serializers.PrimaryKeyRelatedField(queryset=Patient.objects.all())
def create(self, validated_data):
return DataTest.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.label = validated_data.get(‘label’, instance.label)
instance.angle_data = validated_data.get(‘angle_data’, instance.angle_data)
instance.emg_data = validated_data.get(’emg_data’, instance.emg_data)
instance.patient = validated_data.get(‘patient’, instance.patient)
instance.save()
return instance
api.py
class DataTestList(APIView):
def get(self, request, format=None):
dataTests = DataTest.objects.all()
serializer = DataTestSerializer(dataTests, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = DataTestSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.error_messages,
status=status.HTTP_400_BAD_REQUEST)
Finally, I have tested the API and I have sent an JSON example:
{
“label”:”Test”,
“angle_data”:[1.434, 2.243, 3.234],
“emg_data”:[2.3, 2.1],
“patient”:1
}
and I get the following error:
ProgrammingError: column “angle_data” is of type double precision but expression is of type numeric[] LINE 1: …, “created_at”, “modified_at”) VALUES (1, ‘Test’, ARRAY[1.0,… ^
HINT: You will need to rewrite or cast the expression.
I have debugged the API and i have seen that the serializer receives a float list. I suppose that the problem is that the column in PostgreSQL is a double precision and the float list is not codified with double precision.
Could anybody help me?
Greetings and thank you!!
Okay, I understand the problem. You\’re getting a `ProgrammingError` in PostgreSQL because there\’s a type mismatch between the data your serializer is providing (which PostgreSQL infers as `numeric[]`) and the column type in your database (`double precision[]`). Here\’s a breakdown of the issue and how to solve it, along with best practices:\n\n**Understanding the Root Cause**\n\n* **`ArrayField` and Data Types:** The `ArrayField` in Django (backed by PostgreSQL\’s array type) is generally flexible, but PostgreSQL is strict about the data types within the array. It\’s inferring the type based on the literals in the JSON you send.\n* **`serializers.FloatField()`:** While you\’re using `serializers.FloatField()` in your serializer, this only validates that the input *can* be converted to a float. It doesn\’t enforce a specific floating-point precision when the data is written to the database.\n* **PostgreSQL\’s Inference:** When PostgreSQL sees numbers like `1.434`, `2.243`, etc., without explicit casting, it often defaults to treating them as `NUMERIC` (arbitrary precision decimal), rather than `DOUBLE PRECISION` (IEEE 754 double-precision floating-point).\n\n**Solutions**\n\nHere are the most effective ways to resolve this, from simplest to potentially more robust:\n\n**1. Explicit Casting in the Serializer (Recommended and Easiest)**\n\nThe most straightforward fix is to ensure that the floats you\’re passing to the database are explicitly cast to double precision *within the serializer*. Modify your serializer\’s `create` and `update` methods:\n\n“`python\nfrom rest_framework import serializers\nfrom .models import DataTest\nfrom django.contrib.postgres.fields import ArrayField # Import ArrayField if not already imported\n\n\nclass DataTestSerializer(serializers.Serializer):\n pk = serializers.IntegerField(read_only=True)\n label = serializers.CharField()\n angle_data = serializers.ListField(child=serializers.FloatField())\n emg_data = serializers.ListField(child=serializers.FloatField())\n patient = serializers.PrimaryKeyRelatedField(queryset=Patient.objects.all())\n\n def create(self, validated_data):\n # Cast to float to match database field\n validated_data[\’angle_data\’] = [float(x) for x in validated_data[\’angle_data\’]]\n validated_data[\’emg_data\’] = [float(x) for x in validated_data[\’emg_data\’]]\n return DataTest.objects.create(**validated_data)\n\n def update(self, instance, validated_data):\n instance.label = validated_data.get(\’label\’, instance.label)\n instance.angle_data = [float(x) for x in validated_data.get(\’angle_data\’, instance.angle_data)]\n instance.emg_data = [float(x) for x in validated_data.get(\’emg_data\’, instance.emg_data)]\n instance.patient = validated_data.get(\’patient\’, instance.patient)\n instance.save()\n return instance\n“`\n\n**Explanation:**\n\n* We iterate through the `angle_data` and `emg_data` lists *before* saving to the database.\n* `float(x)`: This explicitly converts each element `x` to a Python float, which Django/Psycopg2 will then map to PostgreSQL\’s `DOUBLE PRECISION` type correctly.\n\n**Why this is preferred:**\n\n* **Clarity:** It\’s very clear what\’s happening: you\’re explicitly ensuring the correct data type before the database interaction.\n* **Robustness:** Handles potential type variations in input data.\n* **Serializer Responsibility:** The serializer is the correct place to handle data transformation and preparation.\n\n**2. Database Migration (If you want to change the DB column type – use with caution)**\n\n*This approach is generally not recommended unless you have a very specific reason to change your database schema. It can have broader implications.*\n\nYou could modify your model and create a new migration to explicitly set the data type to `double precision` using a `FloatField` and then use ArrayField.\nIn models.py:\n\n“`python\nfrom django.db import models\nfrom django.contrib.postgres.fields import ArrayField\n\nclass DataTest(models.Model):\n patient = models.ForeignKey(\’Patient\’, on_delete=models.CASCADE) # Recommended to specify on_delete\n label = models.CharField(max_length=36)\n angle_data = ArrayField(models.FloatField())\n emg_data = ArrayField(models.FloatField())\n created_at = models.DateTimeField(auto_now_add=True)\n modified_at = models.DateTimeField(auto_now=True)\n\n def __str__(self): # Use __str__ instead of __unicode__ for Python 3\n return str(self.patient) # Return a string representation\n“`\n\nThen, run:\n\n“`bash\npython manage.py makemigrations\npython manage.py migrate\n“`\n\n**Important Considerations for Migrations:**\n\n* **Data Loss:** If you have existing data in the `angle_data` and `emg_data` columns, changing the column type *could* lead to data loss or truncation if the existing data\’s precision exceeds what `DOUBLE PRECISION` can handle. Back up your database before making schema changes!\n* **Compatibility:** Be sure that this change is compatible with all parts of your application that interact with these columns.\n\n**3. Psycopg2 Type Casting (Less Common, More Complex)**\n\nYou *could* potentially use Psycopg2\’s type casting features directly, but this is generally less maintainable and less clear than handling it within the Django serializer. I wouldn\’t recommend this unless you have a very specific need to do so.\n\n**Example (Illustrative – Not Recommended for this Case):**\n\n“`python\nimport psycopg2.extras\n\n# … inside your view or serializer …\nemg_data = psycopg2.extras.Json(request.data[\’emg_data\’], dumps=lambda x: json.dumps(x, use_decimal=False))\n“`\n\n**Key Improvements & Best Practices in the Code**\n\n* **`__str__` instead of `__unicode__`:** Use `__str__` in your model for Python 3 compatibility. Also, make sure it returns a string!\n* **`on_delete` in ForeignKey:** Always specify `on_delete` behavior for `ForeignKey` fields to prevent data integrity issues. `models.CASCADE` is a common and often appropriate choice (deletes related records).\n* **Error Handling:** Consider adding more robust error handling in your `create` and `update` methods (e.g., `try…except` blocks) to catch potential exceptions during data conversion or database operations.\n* **Use Django\’s ModelSerializers:** For most cases, use Django Rest Framework\’s `ModelSerializer`. This automatically creates fields and validators based on your model, significantly reducing boilerplate code. You might need to adjust the field types explicitly if the automatic inference isn\’t what you want, but it\’s a good starting point.\n\n**Complete Example using ModelSerializer (Highly Recommended):**\n\n“`python\nfrom rest_framework import serializers\nfrom .models import DataTest\nfrom django.db import models\n\nclass DataTestSerializer(serializers.ModelSerializer):\n angle_data = serializers.ListField(child=serializers.FloatField())\n emg_data = serializers.ListField(child=serializers.FloatField())\n class Meta:\n model = DataTest\n fields = \’__all__\’ # Or specify the fields you want to expose\n \n def validate(self, data):\n data[\’angle_data\’] = [float(x) for x in data[\’angle_data\’]]\n data[\’emg_data\’] = [float(x) for x in data[\’emg_data\’]]\n return data\n\n“`\n\nKey changes:\n\n* `serializers.ModelSerializer`: Inherit from this. It automatically creates fields based on your model.\n* `Meta` class: Specify the `model` and `fields` (or `\’__all__\’`)\n* `validate` method: This is the *best* place to perform data cleaning and validation. The `validate` method receives the data *after* individual field validation, but *before* the object is created or updated. We are doing the type conversion in the validate method\n* Removed create and update methods, as they are handled by ModelSerializer.\n\n**How to Test**\n\n1. **Run Migrations:** If you made any changes to your `models.py`, run `python manage.py makemigrations` and `python manage.py migrate`.\n2. **Test Your API Endpoint:** Use a tool like `curl`, Postman, or Insomnia to send a POST request to your API endpoint with the JSON payload.\n3. **Check the Database:** Verify that the data is being stored correctly in your PostgreSQL database with the correct data types. You can use a PostgreSQL client like `psql` or pgAdmin to inspect the table.\n\n**In Summary**\n\nThe best solution is to use explicit casting in the serializer\’s `create` and `update` methods or the `validate` method if you use ModelSerializer. Using ModelSerializer with the `validate` method is the most recommended option. This ensures that the data is converted to the correct type before it is written to the database, preventing the `ProgrammingError`. Avoid changing database column types unless absolutely necessary. Remember to test thoroughly after implementing any of these solutions.\n