Reputation: 7260
I have a MySQL master-slave configuration and I want to delete some old records on master only and keep them on slave:
class MonitorQuerySet(models.QuerySet):
def delete(self, *args, **kwargs):
with connection.cursor() as cur:
cur.execute('SET @@session.sql_log_bin = 0')
ret = super().delete(*args, **kwargs)
cur.execute('SET @@session.sql_log_bin = 1')
return ret
class Monitor(models.Model):
...
objects = models.Manager()
nobinlog = MonitorQuerySet.as_manager()
The problem with this approach is that now one can do Monitor.nobinlog.delete()
and this will wipe out the whole table.
I though about pre_delete
and post_delete
signals but that will run on each call to delete()
.
Is there a better solution for this?
Upvotes: 1
Views: 151
Reputation: 29967
I think you are referring to the fact that you usually have to explicitly spell out Monitor.objects.all().delete()
. You can get that behavior by implementing your manager and queryset separately instead of creating the manager from the queryset with as_manager
, like this:
class NoBinLogQuerySet(models.QuerySet):
def delete(self, *args, **kwargs):
with connection.cursor() as cur:
cur.execute('SET @@session.sql_log_bin = 0')
ret = super().delete(*args, **kwargs)
cur.execute('SET @@session.sql_log_bin = 1')
return ret
class NoBinLogManager(models.Manager):
def get_queryset(self):
return NoBinLogQuerySet(self.model, using=self._db)
class Monitor(models.Model):
...
objects = models.Manager()
nobinlog = NoBinLogManager()
That doesn't protect you from getting the arguments to .filter()
wrong and accidentally deleting your data.
If you have a fixed set of conditions, like "records older than 30 days", I would write a utility method instead of creating an extra manager. In this method, you can hard-code the condition. As an additional safety-measure you can add a dry_run
parameter that will return the records that will be deleted. Something like this:
def delete_old_records(dry_run=True):
kwargs = {'updated_at__lt': now() - timedelta(days=30)}
if dry_run:
ret = Monitor.objects.filter(**kwargs)
else:
with connection.cursor() as cur:
cur.execute('SET @@session.sql_log_bin = 0')
ret = Monitor.objects.delete(**kwargs)
cur.execute('SET @@session.sql_log_bin = 1')
return ret
Depending how you execute this method, consider creating a custom management command.
Upvotes: 1