Repository: mathesar-foundation/mathesar

Issue:
Incorrect error is thrown when id field is not provided for a column object during a batch update
## Description
The following `Patch` request to `/tables/{id}` 

```

{
    "columns": [
        {
            "name": "id",
            "type": "INTEGER"
        },
        {
            "name": "Center",
            "type": "TEXT"
        }
    ]
}
```

 fails with an error `{code: 4999, message: "local variable 'name' referenced before assignment", field: null, detail: null}`.

This is happening due to https://github.com/centerofci/mathesar/blob/a8bad8268c8634bc766fa2e4fde271f4ec1a165e/db/columns/operations/alter.py#L215

always expecting a name which is fetched from the `column id` that is missing from the patch request.

## Expected behavior

`id` field of the column object should be required when batch updating using `/tables/{id}`.

## To Reproduce
Using API 
- Create a table using `/api/db/v0/tables/` API 
- Send a patch request to `/api/db/v0/tables/{id}`

Using frontend client
- Create a table by importing a csv file
- Error will be thrown when clicking on `Finish import`




Discussion:
Can I work on this issue?
@silentninja should the exception be raised inside `_batch_alter_table_rename_columns` method or `_update_id_to_attnum` method? I think raising an exception in `_update_id_to_attnum` makes sense as we are fetching the attnums from the column id in this method. 
@Anish9901 assigned to you, thanks!
@Anish9901 Ideally error should be raised in the serializer `validate` method, Currently, partial_update logic is done at the `view` instead of the `TableSerializer` if you want a challenge you can move the validation and update logic to the serializer. If not it should be validated before calling `table.update_sa_table(serializer.validated_data)`

Resolving patch:
diff --git a/mathesar/api/db/viewsets/tables.py b/mathesar/api/db/viewsets/tables.py
--- a/mathesar/api/db/viewsets/tables.py
+++ b/mathesar/api/db/viewsets/tables.py
@@ -10,7 +10,6 @@
     exceptions as database_api_exceptions,
     base_exceptions as database_base_api_exceptions,
 )
-from mathesar.api.exceptions.generic_exceptions import base_exceptions as base_api_exceptions
 from db.types.exceptions import UnsupportedTypeException
 from mathesar.api.dj_filters import TableFilter
 from mathesar.api.pagination import DefaultLimitOffsetPagination
@@ -31,27 +30,12 @@ def get_queryset(self):
         return Table.objects.all().order_by('-created_at')
 
     def partial_update(self, request, pk=None):
+        table = self.get_object()
         serializer = TableSerializer(
-            data=request.data, context={'request': request}, partial=True
+            table, data=request.data, context={'request': request}, partial=True
         )
         serializer.is_valid(raise_exception=True)
-        table = self.get_object()
-
-        # Save the fields that are stored in the model.
-        present_model_fields = []
-        for model_field in table.MODEL_FIELDS:
-            if model_field in serializer.validated_data:
-                setattr(table, model_field, serializer.validated_data[model_field])
-                present_model_fields.append(model_field)
-        table.save(update_fields=present_model_fields)
-        for key in present_model_fields:
-            del serializer.validated_data[key]
-
-        # Save the fields that are stored in the underlying DB.
-        try:
-            table.update_sa_table(serializer.validated_data)
-        except ValueError as e:
-            raise base_api_exceptions.ValueAPIException(e, status_code=status.HTTP_400_BAD_REQUEST)
+        serializer.save()
 
         # Reload the table to avoid cached properties
         table = self.get_object()
diff --git a/mathesar/api/serializers/tables.py b/mathesar/api/serializers/tables.py
--- a/mathesar/api/serializers/tables.py
+++ b/mathesar/api/serializers/tables.py
@@ -1,6 +1,7 @@
 from django.urls import reverse
 from psycopg2.errors import DuplicateTable
 from rest_framework import serializers, status
+from rest_framework.exceptions import ValidationError
 from sqlalchemy.exc import ProgrammingError
 
 from db.types.base import get_db_type_enum_from_id
@@ -11,6 +12,8 @@
 )
 from mathesar.api.exceptions.database_exceptions.exceptions import DuplicateTableAPIException
 from mathesar.api.exceptions.database_exceptions.base_exceptions import ProgrammingAPIException
+from mathesar.api.exceptions.validation_exceptions import base_exceptions as base_validation_exceptions
+from mathesar.api.exceptions.generic_exceptions import base_exceptions as base_api_exceptions
 from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin
 from mathesar.api.serializers.columns import SimpleColumnSerializer
 from mathesar.models import Table, DataFile
@@ -103,6 +106,35 @@ def create(self, validated_data):
                 raise ProgrammingAPIException(e)
         return table
 
+    def update(self, instance, validated_data):
+        if self.partial:
+            # Save the fields that are stored in the model.
+            present_model_fields = []
+            for model_field in instance.MODEL_FIELDS:
+                if model_field in validated_data:
+                    setattr(instance, model_field, validated_data[model_field])
+                    present_model_fields.append(model_field)
+            instance.save(update_fields=present_model_fields)
+            for key in present_model_fields:
+                del validated_data[key]
+            # Save the fields that are stored in the underlying DB.
+            try:
+                instance.update_sa_table(validated_data)
+            except ValueError as e:
+                raise base_api_exceptions.ValueAPIException(e, status_code=status.HTTP_400_BAD_REQUEST)
+        return instance
+
+    def validate(self, data):
+        if self.partial:
+            columns = data.get('columns', None)
+            if columns is not None:
+                for col in columns:
+                    id = col.get('id', None)
+                    if id is None:
+                        message = "'id' field is required while batch updating columns."
+                        raise base_validation_exceptions.MathesarValidationException(ValidationError, message=message)
+        return data
+
 
 class TablePreviewSerializer(MathesarErrorMessageMixin, serializers.Serializer):
     name = serializers.CharField(required=False)


Test patch: