Lock delete () function - Django - django

Is there an option to disable the delete function in the project object if a non-admin user tries to do this via delete function?
For example:
class Product(models.Model):
name = models.CharField(max_lenght=200)
show = models.BooleanField()
logs = models.TextField()
And now if we have in code of project product.delete() we block it through the model property and check if it is an admin, if it is not an admin add a reflection in the field.:
class Product(models.Model):
name = models.CharField(max_lenght=200)
show = models.BooleanField(default=True)
logs = models.TextField()
def delete(self, *args, **kwargs):
super().save(*args, **kwargs)
if request.user.is_superuser
Product.delete()
else:
show = False
logs = 'ther user try remove this ads in time: 08.11 04.11.2022'
save()

It's the responsibility of the view to check that the user (in request.user) has sufficient privilege to perform the requested operation. There's no standard way for a Django object method to obtain the current user, and a programmer with access through the Django shell >>> obj.delete() has to be prevented by other means (such as the risk of losing his job). There simply is no Django logged-in user in that context, but the DB to which it has access ought not to be a production one.
Out on a limb, it is possible to disable the object's delete() method completely by subclassing it to a no-op or to raise an exception. Deleting such an object would then require other means. Either relying on a CASCADE when some other object was deleted, or using raw SQL (the psql command, if PostgreSQL) to remove its data row from the DB table.
(I haven't done the latter, but there are certain objects in the project I am working on which should never require deletion under any normal circumstances, and for which there are no delete views or similar. They will accumulate at the rate of around one per week in production, and removing ones which are completely stale is a problem which can safely be deferred until the year 2100 or later :-)

Related

Django - Log User Activity (Query - GET/FILTER/UPDATE/DELETE) To Database

I'm building a HIPAA compliant application and I would like to log every user action to the database.
That includes when a user calls a get, filter, or delete object method on a model. I would like to add this functionality to a pre-existing application.
_ = ModelA.objects.get(id=1)
_ = ModelA.objects.filter(age__gt=10)
_ = ModelA.objects.filter(age__lt=5).delete()
I want to log every database operation.
Django Activity Stream does not work for get method.
How do I implement this functionality?
If you look at django-hipaa, it can give you a framework from which to work. I didn't copy it directly, but the idea is to create an abstract logging model--very simplistically:
#in models.py
class Log(models.Model):
user = models.ForeignKey(User, on_delete=PROTECT) #because you want to preserve the log
created_on = models.DateTimeField(auto_now_add=True)
action_taken = models.CharField(max_length=255)
class Meta:
abstract = True
Then for each database that you want to log, create a log model:
#still in models.py
class ModelALog(Log):
row = models.ForeignKey(ModelA, on_delete=Protect)
#whatever other information you want to log
then when in your views.py, you save a log anytime you create a view that involves a database query that you want to log:
#in views.py
def some_view_with_ModelA(request):
something = ModelA.objects.get(pk=1)
log = ModelALog.objects.create(user=request.user,
action_take='your description', row=something)
return render(yourview.html)
This may potentially mean that you have to create additional views to perform the same functions as might otherwise be done from the admin portal, but it ensures all events you want logged are logged.
You might also look into the package django-safedelete as you contemplate deleting, and this package provides a soft-delete so the deleted information is still preserved to protect the integrity of your logs.

Django Manytomany add

I have 2 models in my Project with Many to Many relationship. On saving model Event, I read from the event_attendees file and add it to the attendees field in the Event. No errors/exceptions shown but attendee is not added to the attendees field. Do I need to save the model again after altering with the attendees field? If so, how to do that (calling save method from add_attendees will cause the program into infinite loop)?
class Attendee(models.Model):
name = models.CharField(max_length=100)
class Event(models.Model):
name = models.CharField(max_length=100)
event_attendees = models.FileField(upload_to='documents/', blank=True)
attendees = models.ManyToManyField(Attendee, blank=True)
def save(self, *args, **kwargs):
super().save()
self.add_attendees()
def add_attendees(self):
with open(self.event_attendees.url[1:]) as csv_file:
# Some code here
for row in csv_reader:
# Some code here
attendee = Attendee(name=name)
attendee.save()
self.attendees.add(attendee)
print(self.attendees.all()) # attendee added
print(attendee.event_attended) # event present with attendee
#Refresh template to check changes -> Changes lost
It's the Attendee object that you haven't saved.
You can shortcut it by using the create method on the m2m field:
for row in csv_reader:
self.attendees.create(name=whatever)
(Note, please don't blindly catch exceptions. Django will already do that and report a useful error page. Only catch the exceptions you are actually going to deal with.)
Apparently, the feature worked when I used non-admin web dashboard. While using by-default-created /admin dashboard, this feature was not working. I am assuming from the results that the admin side code calls different methods while saving the model object even though I have overridden the save method (and hence my save method along with other methods should be called). I will update with more info if I find it.

Django creating a "sentinel" user on delete

I do want to keep some info about removed users (like username) to show in forum posts. How can I achieve that?
So far I have this:
class Post(models.Model):
(...)
creator = models.ForeignKey(User, blank=True, null=True,on_delete=models.SET(get_sentinel_user))
#receiver(pre_delete, sender=User, dispatch_uid='user_delete_signal')
def create_sentinel_user(sender, instance, using, **kwargs):
SentinelUser.objects.get_or_create( \
username=instance.username+" (left)")[0]
def get_sentinel_user():
return SentinelUser.objects.latest('id')
However if I use that in Admin, it doesn't work because for some reason get_sentinel_user is run sooner than pre_delete and therefor the sentinel user doesn't exist yet.
If your ultimate goal is to save data about the user, the django user docs suggest to use the is_active flag rather than deleting users. That way, you will maintain access to the user information, will not need to worry about your ForeignKey consistency, and don't have this overhead of creating a dummy user for every user that you delete.
You could then back that up by using on_delete=models.PROTECT to make sure you still protect the consistency of your database.
If for some reason you have to delete your users, I would override the delete() on Post rather than trying to use signals.
So, a sample solution could be something like:
def delete(self):
self.created = SentinelUser.objects.get_or_create(username=self.created.username+" (left)")[0]
self.save()
return super(Post,self).delete(self)

Django delete foreign object?

If we set up a profile how Django recommends:
class Profile(models.Model):
user = models.ForeignKey(User, unique=True)
Then when you delete the User object from Django admin, it deletes his profile too.This is because the profile has a foreign key to user and it wants to protect referential integrity. However, I want this functionality even if the pointer is going the other way. For example, on my Profile class I have:
shipper = models.ForeignKey(Shipper, unique=True, blank=True, null=True)
carrier = models.ForeignKey(Carrier, unique=True, blank=True, null=True)
affiliat = models.ForeignKey(Affiliate, unique=True, blank=True, null=True, verbose_name='Affiliate')
And I want it so that if you delete the Profile it'll delete the associated shipper/carrier/affiliate objects (don't ask me why Django made "affiliate" some weird keyword). Because shippers, carriers and affiliates are types of users, and it doesn't make sense for them to exist without the rest of the data (no one would be able to log in as one).
The reason I didn't put the keys on the other objects, is because then Django would have to internally join all those tables every time I wanted to check which type the user was...
While using a post_delete signal as described by bernardo above is an ok approach, that will work well, I try to avoid using signals as little as humanly possible as I feel like it convolutes your code unnecessarily by adding behavior to standard functionality in places that one might be expecting.
I prefer the overriding method above, however, the example given by Felix does have one fatal flaw; the delete() function it is overriding looks like this:
def delete(self, using=None):
using = using or router.db_for_write(self.__class__, instance=self)
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
collector = Collector(using=using)
collector.collect([self])
collector.delete()
Notice the parameter 'using', in most cases we call delete() with empty arguments so we may have even known it was there. In the above example this parameter is buried by us overriding and not looking at the superclass functionality, if someone where to pass the 'using' parameter when deleting Profile it will cause unexpected behavior. To avoid that, we would make sure to preserve the argument along with its default lika so:
class Profile(models.Model):
# ...
def delete(self, using=None):
if self.shipper:
self.shipper.delete()
if self.carrier:
self.carrier.delete()
if self.affiliat:
self.affiliat.delete()
super(Profile, self).delete(using)
One pitfall to the overriding approach, however, is that delete() does not get explicitly called per db record on bulk deletes, this means that if you are going to want to delete multiple Profiles at one time and keep the overriding behavior (calling .delete() on a django queryset for example) you will need to either leverage the delete signal (as described by bernardo) or you will need to iterate through each record deleting them individually (expensive and ugly).
A better way to do this and that works with object's delete method and queryset's delete method is using the post_delete signal, as you can see in the documentation.
In your case, your code would be quite similar to this:
from django.db import models
from django.dispatch import receiver
#receiver(models.signals.post_delete, sender=Profile)
def handle_deleted_profile(sender, instance, **kwargs):
if instance.shipper:
instance.shipper.delete()
if instance.carrier:
instance.carrier.delete()
if instance.affiliat:
instance.affiliat.delete()
This works only for Django 1.3 or greater because the post_delete signal was added in this Django version.
You can override the delete() method of the Profile class and delete the other objects in this method before you delete the actual profile.
Something like:
class Profile(models.Model):
# ...
def delete(self):
if self.shipper:
self.shipper.delete()
if self.carrier:
self.carrier.delete()
if self.affiliat:
self.affiliat.delete()
super(Profile, self).delete()

Same Table Django ORM Soft Delete Method Okay?

I'm using the following setup to implement soft deletes in Django. I'm not very familiar with Django under the hood so I'd appreciate any feedback on gotchas I might encounter. I'm particular uncomfortable subclassing a QuerySet.
The basic idea is that the first call to delete on a MyModel changes MyModel's date_deleted to the current datetime. A second delete will actually delete the object. (Catching a delete requires two overrides, one on the object and one on the QuerySet, which can bypass an object's delete method.) Since the default manager will hide deleted objects, deleted objects disappear and must be explicitly requested via the deleted_objects manager.
Using this setup requires defining DeletionQuerySet and DeletionManager and adding date_deleted, objects, and deleted_objects to your model(s).
Thanks,
P.S., forgot to mention that this method of filtering objects out of the default manager is strongly discouraged!
class DeletionQuerySet(models.query.QuerySet):
def delete(self):
prev_deleted = self.filter(date_deleted__isnull=False)
prev_deleted.actual_delete()
prev_undeleted = self.filter(date_deleted__isnull=True)
prev_undeleted.update(date_deleted=datetime.datetime.now())
def actual_delete(self):
super(DeletionQuerySet, self).delete()
class DeletionManager(models.manager.Manager):
# setting use_for_related_fields to True for a default manager ensures
# that this manager will be used for chained lookups, a la double underscore,
# and therefore that deleted Entities won't popup unexpectedly.
use_for_related_fields = True
def __init__(self, hide_deleted=False, hide_undeleted=False):
super(DeletionManager, self).__init__()
self.hide_deleted = hide_deleted
self.hide_undeleted = hide_undeleted
def get_query_set(self):
qs = DeletionQuerySet(self.model)
if self.hide_deleted:
qs = qs.filter(date_deleted__isnull=True)
if self.hide_undeleted:
qs = qs.filter(date_deleted__isnull=False)
return qs
class MyModel(models.Model):
# Your fields here...
date_deleted = models.DateTimeField(null=True)
#the first manager defined in a Model will be the Model's default manager
objects = DeletionManager(hide_deleted=True)
deleted_objects = DeletionManager(hide_undeleted=True)
def delete(self):
if self.date_deleted is None:
self.date_deleted = datetime.datetime.now()
self.save()
else:
super(Agreement, self).delete()
I think anything with current in use, popular, technologies, there is no way to have problem domain agnostic, generic soft deletes.
I think it is more linked to historical/history oriented database systems than to what we are used.
I recommend you to not circumvent django's delete (which is a hard delete). Keep as is.
The "delete" that you most likely will have in our system, is in 90% of the case, a visual delete ...
In this regard, try to find synonyms with delete for your specific domain problem and do this from the start of the project.
Because complain that a IsVisible, IsUnpublished (even IsDeleted) mess up your queries, they complain that you must always be careful to include them...
But this is obviously ignorance of the domain problem, if the domain has objects that can be made invisible, or become unpublished - of course when you query the list of all the objects you want to display, you should FROM THE START, QUERY ALL THE OBJECTS that are not visible and unpublished because this is how your domain problem is solved in a complete form.
Cheers.