Django deleting where SET_NULL - django

I have an issue with a ForeignKey and on_delete=SET_NULL.
When deleting the last_data referenced in my Stuff model, it also deletes the stuff object just as if this was a cascade, which is obviously not what I expected, rather setting the last_data field null.
Here is how my models are defined in two different django apps.
# App 1
class Device(models.Model):
last_data = models.ForeignKey('Data', null=True, blank=True, related_name="last_data_device", on_delete=models.SET_NULL, help_text="Latest data")
class Data(models.Model):
content = models.CharField(max_length=255)
date_created = models.DateTimeField(blank=True, null=False, db_index=True)
device = models.ForeignKey(Device, related_name="data", db_index=True, on_delete=models.CASCADE)
# App 2
class Stuff(models.Model):
device = models.OneToOneField(Device, null=True, blank=True, related_name="stuff", db_index=True, on_delete=models.CASCADE)
last_data = models.ForeignKey(Data, null=True, blank=True, help_text="Latest data", db_index=True, on_delete=models.SET_NULL)
I must have misunderstood how this is linked, what I want is that a stuff object is never deleted when data is removed, but that the last_data reference it has may be nulled when this happens.
How should I write this or what did I do wrong here?
Thanks
PS: Migrations are up to date and db is synced.
Well, seing the answers given, already, it seems I should clarify.
When I do :
>>> stuff = Stuff.objects.get(...)
>>> stuff.last_data.delete()
Is that this is the stuff object that gets removed as "dependancy" and I cannot understand why.
What I'd expect is that the last_data field gets nulled and the stuff object is left alone.

Maybe this is due to communication OneToOneField. If you delete your last_data referenced in your Stuff model, where device connet OneToOne with Device. You can set on_delete=models.SET_NULL on your OneToOne field

Change to models.DO_NOTHING. That would avoid the delete.

If I understood correctly, your problem is that when you do:
>>> stuff = Stuff.objects.get(...)
>>> stuff.last_data.delete()
then the corresponding Data record gets removed from the database as well.
This is expected, and it's how it supposed to work. If all you want to do is to set last_data to NULL, while keeping the Data table intact, then:
>>> stuff.last_data = None
>>> stuff.save()
The purpose of on_delete=SET_NULL is to support the opposite use case: when you delete a Data record, then all Stuff records that were pointing to that Data will get their last_data set to NULL:
>>> stuff = Stuff.objects.get(...)
>>> stuff.last_data
<Data: ...>
>>> Data.objects.all().delete()
>>> stuff.last_data
None

Related

What is the best way to handle DJANGO migration data with over 500k records for MYSQL

A migration handles creating two new fields action_duplicate and status_duplicate
The second migration copies the data from the action and status fields, to the two newly created fields
def remove_foreign_keys_from_user_request(apps, schema_editor):
UserRequests = apps.get_model("users", "UserRequest")
for request_initiated in UserRequest.objects.all().select_related("action", "status"):
request_initiated.action_duplicate = request_initiated.action.name
request_initiated.status_duplicate = request_initiated.status.name
request_initiated.save()
The third migration is suppose to remove/delete the old fields action and status
The fourth migration should rename the new duplicate fields to the old deleted fields
The solution here is to remove the dependency on the status and action, to avoid unnecessary data base query, since the status especially will only be pending and completed
My question is for the second migration. The number of records are between 300k to 600k records so I need to know a more efficient way to do this so it doesn't take up all the memory available.
Note: The Database is MySQL.
A trimmed-down version of the UserRequest model
class UserRequest(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
reference = models.CharField(max_length=50, null=True, blank=True)
requester = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
action = models.ForeignKey(Action, on_delete=models.CASCADE)
action_duplicate = models.CharField(
max_length=50, choices=((ACTION_A, ACTION_A), (ACTION_B, ACTION_B)), default=ACTION_A
)
status = models.ForeignKey(ProcessingStatus, on_delete=models.CASCADE)
status_duplicate = models.CharField(
max_length=50,
choices=((PENDING, PENDING), (PROCESSED, PROCESSED)),
default=PENDING,
)
You can work with a Subquery expression [Django-doc], and do the update in bulk:
def remove_foreign_keys_from_user_request(apps, schema_editor):
UserRequests = apps.get_model('users', 'UserRequests')
Action = apps.get_user('users', 'Action')
Status = apps.get_user('users', 'ProcessingStatus')
UserRequests.objects.update(
action_duplicate=Subquery(
Action.objects.filter(
pk=OuterRef('action_id')
).values('name')[:1]
),
status_duplicate=Subquery(
Status.objects.filter(
pk=OuterRef('status_id')
).values('name')[:1]
)
)
That being said, it looks that what you are doing is actually the opposite of database normalization [wiki]: usually if there is duplicated data, you make an extra model where you make one Action/Status per value, and thus prevent having the same value for action_duplicate/status_duplicate multiple times in the database: this will make the database larger, and harder to maintain.
Note: normally a Django model is given a singular name, so UserRequest instead of UserRequests.

SoftDelete in Django

Problem Statement
I have created a base model:
class CreateUpdateDeleteModel(models.Model):
from django.contrib.auth import get_user_model
from django.utils.text import gettext_lazy as _
from .managers import BakeryManager
from drfaddons.datatypes import UnixTimestampField
create_date = UnixTimestampField(_('Create Date'), auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), on_delete=models.PROTECT,
related_name='%(app_label)s_%(class)s_creator')
delete_date = models.DateTimeField(_('Delete Date'), null=True, blank=True)
deleted_by = models.ForeignKey(get_user_model(), on_delete=models.PROTECT, null=True, blank=True,
related_name='%(app_label)s_%(class)s_destroyer')
update_date = models.DateTimeField(_('Date Modified'), auto_now=True)
updated_by = models.ForeignKey(get_user_model(), on_delete=models.PROTECT, null=True, blank=True,
related_name='%(app_label)s_%(class)s_editor')
objects = BakeryManager()
class Meta:
abstract = True
I want that in my system, all the elements are soft deleted i.e. whenever an object is deleted, it should behave as follow:
Behave like normal delete operation: Raise error if models.PROTECT is set as value for on_delete, set null if it's models.SET_NULL and so on.
Set delete_date
Never show it anywhere (incl. admin) in any query. Even model.objects.all() should not include deleted objects.
How do I do this?
I am thinking to override get_queryset() which may solve problem 3. But what about 1 & 2?
Very strange request.
Point 1. It's not clear. Show an error where?
Point 2. The delete is managed as a status and not a hide of the data. I would suggest you add a status field and manage the delete using a special function that changes the status. You can override the delete, but be aware that the delete in queryset does not call the Model.delete but run directly SQL code.
Saying that it's a bad idea. The delete must be there, but just not used. You can easily remove the delete form the Django admin and a developer has no reason to delete code unless he/she is playing with the Django shell irresponsibly. (DB backup?)
Point 3. If you don't want to show the data in admin, simply override the Admin ModelForm to hide the data when the STATUS of the object is DELETE. It's bad design manipulate the domain to accommodate the presentation layer.

how to get all fields in a object.filter() django

I am trying to get all fields to display but im only getting a few of my fields. I am unsure why so here is my views
my view imports
def switchingowners(request):
ownersofcar = Owner.objects.filter(CarID = request.user['CarID'])
for owner in ownersofcar :
addingOwner = models.Owner(CarID=form['CarID'],Owner_Date=ownerofcar['Owner_Date']
)
ok my models look like
class Owner(models.Model):
carID = models.ForeignKey(Car)
Owner_Entry_Number = models.IntegerField()
Owner_Date = models.DateField('Proprietor start date', null=True, blank=True)
Owner_Last_Name = models.CharField(max_length=50, null=True, blank=True)
Owner_First_Name = models.CharField(max_length=50, null=True, blank=True)
Owner_Middle_Initial = models.CharField(max_length=6, null=True, blank=True)
Owner_Address = models.CharField(max_length=80, null=True, blank=True)
my database backend has information in all fields
ownersofcar = Owner.objects.filter(CarID = request.user['CarID'])
it tells me TypeError
and the filtered objects i see are
self
[<Owner: 1248612 MALCOLM DESRIVIERES >, <Owner: 1248612 JULIETTA REMY >, <Owner: 1248B612 THERESA DESIR >, <Owner: 1248B612 ALEXANDER JEAN>]
where on earth are the other fields? i dont see any documentation on secifying which fields i want to receive cause i want them all!
each field has important information
im basically switching all the names from one car to another car/ multiple cars
but filter is not giving back all the fields
I assume you are getting a type error because you are doing request.user['CarId']. Unless you using a custom user class this is not valid.
The fields are all there, what you see is just a string representation of your result as defined by the __str__ or __unicode__ method of Owner.
Either change this method or print the field that you want, e.g.:
for owner in ownersofcar:
logger.error(owner.Owner_Address)
thanks to one of your guys suggestion i realize the error while fields were missing my coworker overrided the str() method and wasnt paying attention to that
and the override is doign exactly what it needs to. not include the other fields
thanks guys

could django update foreignkey use SQL?

I filled datas into postgreSQL without type foreignkey at first.
here is my models.py
class BeverageMenu(models.Model):
brand = models.CharField(max_length=255, null=True)
area = models.CharField(max_length=50, blank=True, null=True)
class DMenu(models.Model):
dmenu = models.ForeignKey(BeverageMenu,null=True,blank=True)
category = models.CharField(max_length=255, null=True)
product = models.CharField(max_length=255, null=True)
and I use this way to update the foreignkey:
>>> from psql.models import BeverageMenu,DMenu
>>> menu1 = BeverageMenu.objects.get(id=1)
>>>product = DMenu.objects.filter(area='North')
>>>product.update(dmenu=menu1)
And I want to know could I use SQL directly to do this ?
I try this but fail
INSERT INTO psql_dmenu(category,product,dmenu) VALUES ('hot','soup',1),
ERROR: column "dmenu" of relation "psql_dmenu" does not exist
You could, but why would you want to? Django has a model layer for a reason, which is to make the database easier to deal with and less dependent on SQL.
However, for your problem, the issue is that the underlying database column for a ForeignKey includes the prefix _id: so your field is dmenu_id.

ManyToManyField using Through and blank=True still required in admin interface

My model (partial code):
class Observation(models.Model):
date = models.DateField()
geom = models.PointField()
values = models.ManyToManyField(Label, through='Value', null=True, blank=True)
objects = models.GeoManager()
class Value(models.Model):
observation = models.ForeignKey(Observation)
label = models.ForeignKey(Label)
value = models.CharField(max_length=100)
objects = models.GeoManager()
When I manage an Observation object in the admin interface, it still says at least one value per observation is required.
Am I doing something wrong, is this a bug, or should I write a derived Admin class to solve this?
I solved this by improving my ERM. The field values in Observation is obsolete, since you get a value_set from the ForeignKey relation in Value.
Still a weird side effect, but since there were no replies I'll consider it something that doesn't occur often.
This is happened to me too.
How exactly you got it resolved?
Following modification seems to do the trick:
But not sure, what effect it's having at DB level.
class Value(models.Model):
label = models.ForeignKey(Label, blank=True, null=True)