Reputation: 320
In a project I have the apps users
and comments
, connected via ForeignKey
.
If I edit a user who doesn't have a comment, I can do that without without any problem. I can even add a comment.
But when I try to save/patch any User data of a User that already has a comment, I get the IntegrityError
.
EDIT:
I actually use a PUT
method both times.
users\models.py
:
class User(AbstractBaseUser):
created_at = models.DateTimeField(auto_now_add=True, editable=False))
....
#nothing special here
comments\models.py
:
class Comments(models.Model):
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True, editable=False)
modified_at = models.DateTimeField(auto_now=True, editable=False)
created_by = models.ForeignKey(User, null=True, editable=False, related_name='%(class)s_created', on_delete=models.SET_NULL)
modified_by = models.ForeignKey(User, null=True, editable=False, related_name='%(class)s_modified', on_delete=models.SET_NULL)
type = models.CharField(max_length=50, blank=True, default='')
content = models.CharField(max_length=100, blank=True, default='')
owner = models.ForeignKey(User, null=True, blank=True, related_name='comments', on_delete=models.SET_NULL)
The Trace in the Error Message points to the line cmnt.save()
in the function update
in the users\serializers.py
, looking like this:
def update(self, instance, validated_data):
info = model_meta.get_field_info(instance)
#extract nested user information to handle those separately
...
...
comments_data = validated_data.pop('comments', None)
...
...
# update user fields
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
m2m_fields = []
for attr, value in validated_data.items():
if attr in info.relations and info.relations[attr].to_many:
m2m_fields.append((attr, value))
else:
setattr(instance, attr, value)
instance.save()
# Note that many-to-many fields are set after updating instance.
# Setting m2m fields triggers signals which could potentially change
# updated instance and we do not want it to collide with .update()
for attr, value in m2m_fields:
field = getattr(instance, attr)
field.set(value)
update_session_auth_hash(self.context.get('request'), instance)
#create, update, delete related entities
if comments_data and comments_data is not None:
#remove items
cmnt_ids = [item.get('id') for item in comments_data]
for comment in instance.comments.all():
if comment.id not in cmnt_ids:
cmnt.delete()
#create or update items
for comment in comments_data:
cmnt = Comment(owner=instance, **comment)
cmnt.save() # <--- Trace points here
else:
#delete all items
for comment in instance.comments.all():
comment.delete()
The complete Trace:
Traceback (most recent call last):
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\backends\mysql\base.py", line 74, in execute
return self.cursor.execute(query, args)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\MySQLdb\cursors.py", line 209, in execute
res = self._query(query)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\MySQLdb\cursors.py", line 315, in _query
db.query(q)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\MySQLdb\connections.py", line 239, in query
_mysql.connection.query(self, query)
MySQLdb._exceptions.OperationalError: (1048, "Column 'created_at' cannot be null")
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\rest_framework\viewsets.py", line 114, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\rest_framework\views.py", line 505, in dispatch
response = self.handle_exception(exc)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\rest_framework\views.py", line 465, in handle_exception
self.raise_uncaught_exception(exc)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\rest_framework\views.py", line 476, in raise_uncaught_exception
raise exc
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\rest_framework\views.py", line 502, in dispatch
response = handler(request, *args, **kwargs)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\rest_framework\mixins.py", line 68, in update
self.perform_update(serializer)
File "C:\Users\user2\projects\nano_py36dj3\users\views.py", line 135, in perform_update
serializer.save(modified_by=self.request.user)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\rest_framework\serializers.py", line 207, in save
self.instance = self.update(self.instance, validated_data)
File "C:\Users\user2\projects\nano_py36dj3\users\serializers.py", line 293, in update
dig.save()
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\models\base.py", line 749, in save
force_update=force_update, update_fields=update_fields)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\models\base.py", line 787, in save_base
force_update, using, update_fields,
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\models\base.py", line 868, in _save_table
forced_update)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\models\base.py", line 920, in _do_update
return filtered._update(values) > 0
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\models\query.py", line 771, in _update
return query.get_compiler(self.db).execute_sql(CURSOR)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\models\sql\compiler.py", line 1502, in execute_sql
cursor = super().execute_sql(result_type)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\models\sql\compiler.py", line 1154, in execute_sql
cursor.execute(sql, params)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\backends\utils.py", line 100, in execute
return super().execute(sql, params)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\backends\utils.py", line 68, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\backends\utils.py", line 77, in _execute_with_wrappers
return executor(sql, params, many, context)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\backends\utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "C:\Users\user2\Envs\projpy36dj3\lib\site-packages\django\db\backends\mysql\base.py", line 79, in execute
raise utils.IntegrityError(*tuple(e.args))
django.db.utils.IntegrityError: (1048, "Column 'created_at' cannot be null")
in users\views.py
:
class UserViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsSpecialistAllOrCustomerRead,)
...
...
def retrieve(self, request, *args, **kwargs):
retrieve_type = self.request.query_params.get('retrieve_type', None)
if retrieve_type is None:
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
elif retrieve_type == 'news_counts' and User.objects.filter(id=request.user.id).filter(groups__id=1):
instance = self.get_object()
user_id = instance.id
response_dict = dict()
sql = """
SELECT COUNT(o.id) as requests from projects_project o
LEFT JOIN projects_project_participants op ON (o.id = op.project_id)
WHERE op.project_id IS NULL;
"""
response_dict.update(executeRawSQL(sql)[0])
sql = """
SELECT COUNT(r.id) as reminders
FROM reminders_reminder r, reminders_reminder_participants rp
WHERE r.id = rp.reminder_id AND rp.user_id = {0} AND r.done = 0 AND r.date_time <= CURDATE();
""".format(*(user_id,))
response_dict.update(executeRawSQL(sql)[0])
return Response(response_dict)
else:
return Response(dict())
def perform_create(self, serializer):
if self.request.user.is_anonymous:
serializer.save()
else:
serializer.save(created_by=self.request.user)
def perform_update(self, serializer):
serializer.save(modified_by=self.request.user) # <--- Traced to here
def perform_destroy(self, instance):
for project in instance.projects.all():
#print dir(project)
project.customer = None
project.save()
instance.delete()
Upvotes: 0
Views: 2751
Reputation: 997
The error arises due to the type of update call made.
PUT method updates all the fields, whereas PATCH is used for updating a single field. update() method accepts both PUT and PATCH requests and differentiates it using value of "partial".
Since you are using ModelViewSet , it will automatically set partial=True on PATCH request so you need not worry about it. You just need to send a PATCH API call instead of PUT.
For more info on ModelViewSet : DRF ModelViewSet.
For partial arguments if using other methods in ViewSet : DEF: Partial Updates
Upvotes: 1