Django: Getting succefully deleted message even if deletion is prevented - python

class SomeModel(models.Model):
end = models.DateTimeField()
def delete(self, *args, **kwargs):
now = datetime.datetime.now()
if self.end < now:
return # past events cannot be deleted
super(SomeModel, self).delete(self, *args, **kwargs)
I've wrote above code in one of my models.
It's working beautifully but having one single problem:
I'm getting a message saying, object is successfully deleted even if that model is not deleted because if the condition I put in.
Is there a way I can send a message that object is not deleted in this case?
NB: This model is for django-admin only.

The delete view in the django admin does not check to see if the delete() call was successful, so if you want to override the delete method as in your question, you'll need to override the entire ModelAdmin.delete_view method.
If SomeModel is only used in the Django admin, another possible approach is to override the has_delete_permission method. This would remove the delete links from the change view, and disable the delete page for events in the past.
class SomeModelAdmin(admin.ModelAdmin):
...
def has_delete_permission(self, request, obj=None):
"""
Return False for events in the past
"""
if obj is None:
# obj is None in the model admin changelist view
return False
now = datetime.datetime.now()
if obj.end < now:
return False # past events cannot be deleted
else:
return super(SomeModelAdmin, self).has_delete_permission(request, obj)
The implementation above would disable the "delete selected objects" admin action, as we return False when obj is None. You should consider doing this anyway, as it calls the queryset delete method and not your overridden delete method.
With this approach, superadmins would still be able to delete events as they have all permissions. I don't think this approach would work if SomeModel appears in model inlines -- although I see that has_delete_permission is an InlineModelAdmin option in Django 1.4.

You could return True or False from your overridden delete() and just work with the value of that within your form to build your message.
def delete(self, *args, **kwargs):
now = datetime.datetime.now()
if self.end < now:
return False # past events cannot be deleted
super(SomeModel, self).delete(self, *args, **kwargs)
return True #successfully deleted from the database

Related

Django ListView - getting the most recent related object and preventing Django from caching the view or database query

I have a Campaign model and a CampaignStatus model whose foreign key is the Campaign model. When a Campaign is edited or created it will pass through several statuses and will have a CampaignStatus object associated with each status change.
Using Django's CBVs, I have a list view that shows a users Campaigns, and I want to pass the most recent status in the context to the template.
Django seems to be caching the status and I don't know how to prevent it. (Possibly relevant: the Django admin campaign view also has the same caching problem - I've defined a method to get the most recent status. The Django admin CampaignStatus list view behaves as expected, always showing new statuses as soon as they're created.)
I would like the cache to be 5 seconds, but it appears to be about 3 minutes. How can I change this?
A code snippet from the generic ListView we're using:
#method_decorator(cache_page(5), name="dispatch") # single arg is seconds
class CampaignsListView(LoginRequiredMixin, ListView):
model = Campaign
paginate_by = 100
template_name = "writing/user_campaigns.html"
context_object_name = "user_campaigns"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
for i, _ in enumerate(context.get("user_campaigns")):
campaign = context["user_campaigns"][i]
campaign_status = CampaignStatus.objects.filter(campaign=campaign).latest("-status")
context["user_campaigns"][i].status = campaign_status.get_status_display()
return context
def get_queryset(self):
return Campaign.objects.filter(user=self.request.user).order_by("-modified")
#... some other methods too
I think your can find better solution, but u can write post_save signal dropping invalidate cache.
Somthing like that:
from django.core.cache import cache
def invalidate_cache_page(
view: str,
path_params: Optional[Dict[str, Any]] = None,
key_prefix: str = '',
partial: bool = True
) -> None:
"""
Invalidate page cache.
"""
cache_key = generate_cache_key(view, path_params, {}, key_prefix, partial)
if hasattr(cache, 'keys'):
keys = cache.keys(cache_key) or []
cache.delete_many(keys)
else:
cache.delete(cache_key)
generate_cache_key your own func for generating

Remove 'Delete selected model' button from related model fields in model creation form (Django admin)

In my models I have Document model with foreign key to the Library model.
When I am in Django admin site I want to disable editing and deleting Library instances when I am creating new Document.
What I tried was to remove delete and edit permissions by subclassing django.contrib.admin.ModelAdmin and removing change/delete permissions
#admin.register(Library)
class LibraryAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
This makes unwanted buttons disappear but also entirely blocks possibility of editing and removing Libraries, which is not what I want. Is there a way to disable these actions only in model edit form?
You could mark the request in the document admin:
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
request._editing_document = object_id is not None # add attribute
return super(DocumentAdmin, self).changeform_view(request, object_id=object_id, form_url=form_url, extra_context=extra_context)
Now you can access that flag in the related admin:
#admin.register(Library)
class LibraryAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
if getattr(request, '_editing_document', False): # query attribute
return False
return super(LibraryAdmin, self).has_delete_permission(request, obj=obj)
Another variation, similar to that of schwobaseggl, would be:
#admin.register(Library)
class LibraryAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
r = super(LibraryAdmin, self).has_delete_permission(request,obj)
if r:
referer = request.path
# Here we can check all the forms were we don`t want to allow Library deletion
if 'documentappname/document/' in referer:
r = False
return r
Pros: you only have to make a function, where you can avoid deleting in many editting pages for different models.
Cons: it relies on the url pattern of your admin app, so, if it changes app or model name (strange but possible) you would have to change it. Another con is that is less fine-grained: you cannot choose to avoid deletion based on some property of the object to be deleted. You coud do this with schwobaseggl's proposal.

Django - access ManyToManyField right after object was saved

I need to notify users by email, when MyModel object is created. I need to let them know all attributes of this object including ManyToManyFields.
class MyModel(models.Model):
charfield = CharField(...)
manytomany = ManyToManyField('AnotherModel'....)
def to_email(self):
return self.charfield + '\n' + ','.join(self.manytomany.all())
def notify_users(self):
send_mail_to_all_users(message=self.to_email())
The first thing I tried was to override save function:
def save(self, **kwargs):
created = not bool(self.pk)
super(Dopyt, self).save(**kwargs)
if created:
self.notify_users()
Which doesn't work (manytomany appears to be empty QuerySet) probably because transaction haven't been commited yet.
So I tried post_save signal with same result - empty QuerySet.
I can't use m2mchanged signal because:
manytomany can be None
I need to notify users only if object was created, not when it's modified
Do you know how to solve this? Is there some elegant way?

Wagtail ModelAdmin read only

Using Wagtails Modeladmin:
Is there any way to disable edit & delete options leaving only the inspect view?
A possible approach that I can think of, is extending the template, removing the edit & delete buttons and then somehow disable the edit and delete view.
Is there any cleaner approach?
EDIT: Thanks to Loic answer I could figure out.
The PermissionHelper source code was also very helpful to figure out the correct method to override.
Complete answer for only showing inspect view
class ValidationPermissionHelper(PermissionHelper):
def user_can_list(self, user):
return True
def user_can_create(self, user):
return False
def user_can_edit_obj(self, user, obj):
return False
def user_can_delete_obj(self, user, obj):
return False
class ValidationAdmin(ModelAdmin):
model = Validation
permission_helper_class = ValidationPermissionHelper
inspect_view_enabled = True
[...]
Sadly, you need at least one of the add, change or delete permission on that model (set within the roles) for it to show up.
The way around that is to provide a custom permission helper class to your ModelAdmin and always allow listing (and still allow add/change/delete to be set within the roles):
class MyPermissionHelper(wagtail.contrib.modeladmin.helpers.PermissionHelper):
def user_can_list(self, user):
return True # Or any logic related to the user.
class MyModelAdmin(wagtail.contrib.modeladmin.options.ModelAdmin):
model = MyModel
permission_helper_class = MyPermissionHelper
modeladmin_register(wagtail.contrib.modeladmin.options.MyModelAdmin)

Using get_inline_instances overwrites add permission?

I currently have a bunch of inlines that all inherit from a base Inline Class set up like this:
class BaseInlineAdmin(admin.TabularInline):
extra = 0
def has_delete_permission(self, request, obj=None):
return False
def has_add_permission(self, request):
return False
I now want to change my admin so that depending on the user, the inlines change like so:
class PartyAdmin(admin.ModelAdmin):
inline1 = [Inline1, Inline2]
inline2 = [Inline1, Inline2, Inline3]
def get_inline_instances(self, request, obj=None):
if request.user.is_staff:
return [inline(self.model, self.admin_site) for inline in self.inline1]
else:
return [inline(self.model, self.admin_site) for inline in self.inline2]
However, when I do this, the has_add_permission no longer works. When I don't use get_inline_instances, the user has no ability to add another item and I would like this functionality to stay consistent. Is there any reason this is not carrying over when I use this method?
For the record, has_delete_permission remains False which makes the situation even weirder.
get_inline_instances is the method that checks the permission. If you want to check for permissions, you either have to call super().get_inline_instances(), or you need to replicate the code from the original function.

Categories

Resources