Delete in admin override with confirmation. - python

I had to override the delete method for admin like so :
def fully_delete_selected_photos(self, request, queryset):
# Code to do my specific delete method.
fully_delete_selected_photos.short_description = "Delete Selected Photos"
class VehiclePhotoAdmin(admin.ModelAdmin):
search_fields = ('listing_id',)
list_display = ('listing_id', 'disp_VehiclePhoto')
actions = [fully_delete_selected_photos]
def get_actions(self, request):
actions = super(VehiclePhotoAdmin, self).get_actions(request)
del actions['delete_selected']
return actions
This works fine but I lost the confirmation of deletion. So when the user selects my "Delete Selected Photos" option it immediate just goes through with the delete. How can I get some kind of confirmation for the overridden deletion? I am confused about the redirections of views in admin.
Thank you for your time!

You can return HttpResponseRedirect in fully_delete_selected_photos method, and make custom view , thats inherit template from admin, and takes queryset parameter, and lists all selected photos.
But if you want to simplify it, add some JavaScript to admin view.

Related

Wagtail admin - playing with urls

I created in wagtail a model called Regbox in model.py and also RegboxModelAdmin in wagtail_hooks.py. Wagtail admin includes item Regbox in wagtail side bar menu. Then I programmatically created a new collection, a new group with permissions add, edit, delete Regbox and this group is assigned to new user after registration. New user can add (edit and delete) new Regbox (model Regbox has forein key User) and this new user can see in wagtail admin only his own regboxes (I used queryset filter so that superuser could see all regboxes in wagtail admin and the current user only his own regboxes). But if this new user plays with urls in his browser he can see also other regboxes (not only his own regboxes). Simply he can change url from for example /regbox/edit/5/ to /regbox/edit/8/ and he can see this page although this page/regbox belongs to another user (it would need here Permission denied). Could someone please advice me how can I do it in wagtail admin so that any user can see only his own regbox pages ? Thanks
Permissions in Django (and Wagtail by extension) are handled on a per model basis, not per instance. Therefore, giving edit permissions on the Regbox to a user will allow him/her to edit every instances of that model. There are a few exceptions in Wagtail (like the Page model).
Anyway, you should be able to achieve what you want and add custom permission check by attaching a custom permission helper class to you ModelAdmin definition.
from wagtail.contrib.modeladmin.helpers import PermissionHelper
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from .models import Regbox
class RegboxPermissionHelper(PermissionHelper):
def user_is_owner(self, user, obj):
if user.pk == obj.owner:
return True
else:
return False
def user_can_inspect_obj(self, user, obj):
"""
Return a boolean to indicate whether `user` is permitted to 'inspect'
a specific `self.model` instance.
"""
return self.user_is_owner(user, obj) && super().user_can_inspect_obj(user, obj)
def user_can_edit_obj(self, user, obj):
"""
Return a boolean to indicate whether `user` is permitted to 'change'
a specific `self.model` instance.
"""
return self.user_is_owner(user, obj) && super().user_can_edit_obj(user, obj)
def user_can_delete_obj(self, user, obj):
"""
Return a boolean to indicate whether `user` is permitted to 'delete'
a specific `self.model` instance.
"""
return self.user_is_owner(user, obj) && super().user_can_delete_obj(user, obj)
class RegboxModelAdmin(ModelAdmin):
model = Regbox
permission_helper_class = RegboxPermissionHelper
modeladmin_register(RegboxModelAdmin)
As you can see, we create a new RegboxPermissionHelper helper class and define methods for the inspect, edit and delete permissions which first check that the user is the owner (this has been extracter to its own method) an then call super to let the original permission check happen. The call to super is important so it return false if the user is not the owner, but it will also return false the user is the owner but doesn't have a particular permission (e.g. you can create and edit but not delete).
FWIW, I think you are using the correct mechanism to filter the list view by filtering the queryset so don't change anything there.

Prevent repopulation and/or resubmit of Django form after using the back button

The Problem
We have the following setup.
Pretty standard Django class based view (inherits from CreateView, which is what I'll call it form now on).
After a successful POST and form validation, the object is created, and the user is redirect_to'd the DetailView of the created record.
Some users decide that they are not happy with the data they entered. They press the back button.
The HTML generated by the CreateView is fetched form browser cache, and repopulated with the data they entered.
To the user, this feels like an edit, so they change the data and submit again.
The result is 2 records, with minor differences.
What have we tried?
At first I thought the Post-Redirect-Get (PRG) pattern that Django uses was supposed to prevent this. After investigating, it seems that PRG is only meant to prevent the dreaded "Do you want to resubmit the form?" dialog. Dead end.
After hitting the back button, everything is fetched from cache, so we have no chance of interacting with the user from our Django code. To try and prevent local caching, we have decorated the CreateView with #never_cache. This does nothing for us, the page is still retrieved form cache.
What are we considering?
We are considering dirty JavaScript tricks that do an onLoad check of window.referrer, and a manual clean of the form and/or notice to user if the referrer looks like the DetailView mentioned earlier. Of course this feel totally wrong. Then again, so do semi-duplicate records in our DB.
However, it seems so unlikely that we are the first to be bothered by this that I wanted to ask around here on StackOverflow.
Ideally, we would tell the browser that caching the form is a big NO, and the browser would listen. Again, we already use #never_cache, but apparently this is not enough. Happens in Chrome, Safari and Firefox.
Looking forward to any insights! Thanks!
Maybe don't process the POST request when it's coming from a referrer other than the same page?
from urllib import parse
class CreateView(...):
def post(self, *args, **kwargs):
referer = 'HTTP_REFERER' in self.request.META and parse.urlparse(self.request.META['HTTP_REFERER'])
if referer and (referer.netloc != self.request.META.get('HTTP_HOST') or referer.path != self.request.META.get('PATH_INFO')):
return self.get(*args, **kwargs)
...
I know I'm late to this party but this may help anybody else looking for an answer.
Having found this while tearing my hair out over the same problem, here is my solution using human factors rather than technical ones. The user won't use the back button if after submitting from a CreateView, he ends up in an UpdateView of the newly created object that looks exactly the same apart from the title and the buttons at the bottom.
A technical solution might be to create a model field to hold a UUID and create a UUID passed into the create form as a hidden field. When submit is pressed, form_valid could check in the DB for an object with that UUID and refuse to create what would be a duplicate (unique=True would enforce that at DB level).
Here's example code (slightly redacted to remove stuff my employer might not want in public). It uses django-crispy-forms to make things pretty and easy. The Create view is entered from a button on a table of customers which passes the customer account number, not the Django id of its record.
Urls
url(r'enter/(?P<customer>[-\w]+)/$', JobEntryView.as_view(), name='job_entry'),
url(r'update1/(?P<pk>\d+)/$', JobEntryUpdateView.as_view(), name='entry_update'),
Views
class JobEntryView( LoginRequiredMixin, CreateView):
model=Job
form_class=JobEntryForm
template_name='utils/generic_crispy_form.html' # basically just {% crispy form %}
def get_form( self, form_class=None):
self.customer = get_object_or_404(
Customer, account = self.kwargs.get('customer','?') )
self.crispy_title = f"Create job for {self.customer.account} ({self.customer.fullname})"
return super().get_form( form_class)
def form_valid( self, form): # insert created_by'class
#form.instance.entered_by = self.request.user
form.instance.customer = self.customer
return super().form_valid(form)
def get_success_url( self):
return reverse( 'jobs:entry_update', kwargs={'pk':self.object.pk, } )
# redirect to this after entry ... user hopefully won't use back because it's here already
class JobEntryUpdateView( LoginRequiredMixin, CrispyCMVPlugin, UpdateView):
model=Job
form_class=JobEntryForm
template_name='utils/generic_crispy_form.html'
def get_form( self, form_class=None):
self.customer = self.object.customer
self.crispy_title = f"Update job {self.object.jobno} for {self.object.customer.account} ({self.object.customer.fullname})"
form = super().get_form( form_class)
form.helper[-1] = ButtonHolder( Submit('update', 'Update', ), Submit('done', 'Done', ), )
return form
def get_success_url( self):
print( self.request.POST )
if self.request.POST.get('done',None):
return reverse('jobs:ok')
return reverse( 'jobs:entry_update',
kwargs={'pk':self.object.pk, } ) # loop until user clicks Done

i need solution for users of the Django admin page

I created two users on the django admin page. When one user enters data the other user can see the data.
So,every users can see and change each other datas.
I want each user can only see and change own datas not others.
How can i prevent it?
I think that you don't have to use django admin as user profile. You can create authorization/authentication system on your website, this is the great post about it.
Then you can create user profile for every users, where they can add/edit/delete their private datas.
You can change the attributes (columns) according to different users (based on permission or some user attribute) but can not filter tuples on basis of that.
Example - (in app_name/admin.py)
def get_readonly_fields(self, request, obj=None):
if request.user.is_superuser:
self.readonly_fields = ()
else:
self.readonly_fields = ('id', 'name', 'attr1', 'attr2')
return self.readonly_fields
But looking on the limited info given, i suppose you want to filter tuples. This can only be achieved by making a custom admin page parallel to django's admin (ex - name it staff panel). In its view method filter (using Querysets) according to the school and return's its related data for edit or whatever you want (for ease you can use Model forms).
EDIT: Sorry, i found exactly what you were asking for(hopefully).
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(MyModelAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
This returns only the objects related to logged in user on the admin change page.

How can i have delete and edit link in django admin change list page

I am using django admin.
But there are currenly no delete links infront of every row. they have delete selected thing but i want to have delete and edit with every row of model.
how can i do that in django admin
class MyAdmin(models.ModelAdmin):
list_display = ('other_field', 'delete_link', 'edit_link')
def delete_link(self, obj):
info = obj._meta.app_label, obj._meta.module_name
url = reverse('admin:%s_%s_delete' % info, args=(obj.id,))
return 'Delete' % url
delete_link.allow_tags = True
delete_link.short_description = 'Delete'
def edit_link(self,obj):
return u'Edit' % (
obj._meta.app_label, obj._meta.module_name, obj.id)
edit_link.allow_tags = True
edit_link.short_description = "Edit"
UPDATE:
def action_link(self, obj):
app_name = obj._meta.app_label
url_name = obj._meta.module_name
data_id = obj.id
return """
<ul>
<li>Edit</li>
<li>Delete</li>
</ul>
""".format(
obj._meta.app_label,
obj._meta.module_name,
obj.id)
action_link.allow_tags = True
action_link.short_description = 'Actions'
Fields can be made editable inline within Django admin using the list_editable admin option: https://docs.djangoproject.com/en/1.4/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_editable
However, there's no built-in option to add a delete link to every row. That's something you would need to add yourself. You would need to use a combination of a property on the model to add to the list_display in order to get the 'Delete' text in the change list, plus some JavaScript to confirm the delete. I wouldn't fire the delete in a single action.
It's up to you whether you want to fire the delete action via Ajax, or if you want to redirect them to the delete view, etc. Hope that gets you started.

Custom actions in Django Admin

In my Django app, I have a Newsletter model. Now I'd like to be able to send the newsletter (and even resend it) from Django Admin.
I could do this with a hook on the Model.save() method but is there another way that is not tied to the Model?
Thanks.
Admin actions allow you to easily hook up custom actions which can be performed on selected items from the admin's list pages.
If you are doing it from the admin then you'll need to override the save() method, but it can be the AdminModel save... doesn't need to be the full Model save.
However, if you are emailing a lot of emails, a better approach would be to install django-mailer which puts emails into a queue for later processing and then provides you with a new management command: send_mail.
So once you're ready to send the newsletter out you can manually run python manage.py send_mail. Any emails with errors will be moved to a deferred queue where you can retry sending them later.
You can automate this by running manage.py send_mail from cron.
If you really want to get fancy and do it from admin site, install django-chronograph and set up your send_mail schedule from there.
2022 Update:
django.__version__ == 4.1 and maybe ealier.
You can add a custom action at /admin/.../change by overriding django.contrib.admin.options.ModelAdmin._changeform_view:
from django.contrib import admin
class MyModelAdmin(admin.ModelAdmin):
def _changeform_view(self, request, object_id, form_url, extra_context):
if '<your-action>' in request:
# 1. check permissions
# 2. do your thing
print(request)
return super()._changeform_view(request, object_id, form_url, extra_context)
You can now add simple sumbit button to your form with your custom action:
<input type="submit" name="<your-action>" value="<button-title>">
Advantage
Zero javascript.
Authentication (CSRF) protected.
All stock admin actions will work with your form (add, save and continue, ...)
You can try this YouTube tutorial. Just change:
def available(modeladmin, request, queryset):
queryset.update(status='ava')
def not_available(modeladmin, request, queryset):
queryset.update(status='not')
to something like this:
def send(modeladmin, request, queryset):
for data in queryset:
subject=data.title
message=data.mesage
for d in Users.objects.filter(newsletter=True):
email=d.email
sendemail = EmailMessage(
subject, message + unsubscribe,
'emailo#mdjangotutsme.com',
[email], [],
headers = {'Reply-To': 'emailo#mdjangotutsme.com'}
)
sendemail.content_subtype = "html"
sendemail.send()
You can create custom django admin actions.
For example, I have Person model below:
# "store/models.py"
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return self.name
Now, I created uppercase() and lowercase() admin actions in Person admin as shown below. *actions = ("uppercase", "lowercase") is necessary to display uppercase() and lowercase() admin actions:
# "store/admin.py"
from django.contrib import admin, messages
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
actions = ("uppercase", "lowercase") # Necessary
#admin.action(description='Make selected persons uppercase')
def uppercase(modeladmin, request, queryset):
for obj in queryset:
obj.name = obj.name.upper()
obj.save()
messages.success(request, "Successfully made uppercase!")
#admin.action(description='Make selected persons lowercase')
def lowercase(modeladmin, request, queryset):
for obj in queryset:
obj.name = obj.name.lower()
obj.save()
messages.success(request, "Successfully made lowercase!")
Then, if I select 2 persons and Make selected persons uppercase then click on Go as shown below:
I can make 2 persons uppercase as shown below:
Then, if I select 2 persons and Make selected persons lowercase then click on Go as shown below:
I can make 2 persons lowercase as shown below:
In addition, if I remove #admin.action() as shown below
# "store/admin.py"
# ...
# #admin.action(description='Make selected persons uppercase')
def uppercase(modeladmin, request, queryset):
for obj in queryset:
obj.name = obj.name.upper()
obj.save()
messages.success(request, "Successfully made uppercase!")
# #admin.action(description='Make selected persons lowercase')
def lowercase(modeladmin, request, queryset):
for obj in queryset:
obj.name = obj.name.lower()
obj.save()
messages.success(request, "Successfully made lowercase!")
Actual function names are displayed as shown below:

Categories