I am upgrading a Django project from Django 1.11. I have successfully upgraded the project upto Django 2.1. When I upgraded to Django 2.2, I got this error message
"(admin.E130) name attributes of actions defined in class AdimClass(not real name) must be unique"
The admins classes are
class AAdmin(admin.ModelAdmin)
def custom_action(self, request, queryset):
# perform custom action
.....
def custom_action_2(self, request, queryset):
# another custom actions
.....
action = [custom_action, custom_action_2]
class BAdmin(AAdmin):
def custom_action(self, request, queryset):
# performs different actions but has the same name as AAdmin action
.....
actions = AAdmin.actions + [custom_action]
problem: (admin.E130) name attributes of actions defined in class AdimClass(not real name) must be unique
If I remove the custom_action from AAdmin, the error is resolved but the action is no more available for other classes which inherits AAdmin.
Goal: keep the action in parent class AAdmin and override it on child class BAdmin.
Note: The code is working fine upto Django 2.1.
The issue is that you are trying to add the same action name "custom_action" to BAdmin twice, the first is inherited by AAdmin. The solution is to not include the duplicate action. A possible solution:
class BAdmin(AAdmin):
def get_actions(self, request):
actions = AAdmin.actions
if 'custom_action' in actions:
del actions['custom_action']
return actions + [custom_action]
Related
I merged together two views that almost does the same thing except the permission, I want to change permission based on: if company id is in the params. If not, it would use a simple IsAuthenticated class and also created a permission for IsCompany.
class FilesView(ListAPIView):
serializer_class = FileSerializer
permission_classes = (IsAuthenticated,)
...
def get_queryset(self):
if 'company' in self.request.query_params:
# In this case I want the IsCompany Permission class
return get_company_files(self)
# Otherwise the regular one
return get_personal_files(self)
See custom permissions at DRF documentations.
A little example:
from rest_framework import permissions
class IsCompanyRequestBasedPermission(permissions.BasePermission):
message = '<description of your permission>'
def has_permission(self, request, view):
if 'company' in self.request.query_params:
# Make your decision.
And then add it to permission_classes. It would work just as you expect.
If you use
class FilesView(ModelViewSet):
instead of
class FilesView(ListAPIView)
you can use get_serializer_class method which could help you
For example
def get_serializer_class(self):
if "your statement":
return "FirstSerializer"
if "your statement":
return "SecondSerializer"
I'm wondering if there is a way in Wagtail to enter a custom template path via CharField in a base model, and then establish a template in an inherited model that would be the default. For example:
base/models.py
class WebPage(Page):
template_path = models.CharField()
def get_template(self, request):
if self.template_path:
template = template_path
else:
template = *something*
app/models.py
class MyWebPage(WebPage):
template = 'default_template.html'
Ideally, I'd establish the template attribute in the MyWebPage model, and that would act as a default. However, the get_template method in the WebPage base model would supersede it, but only if it's not empty. Is any of this possible?
I was reading through the Wagtail Docs and found this page (http://docs.wagtail.io/en/v2.1.1/advanced_topics/third_party_tutorials.html) and on that page was an article about dynamic templating. This is the page that has it: https://www.coactivate.org/projects/ejucovy/blog/2014/05/10/wagtail-notes-dynamic-templates-per-page/
The idea is to set a CharField and let the user select their template. In the following example they're using a drop down, which might even be better for you.
class CustomPage(Page):
template_string = models.CharField(max_length=255, choices=(
(”myapp/default.html”, “Default Template”),
(”myapp/three_column.html”, “Three Column Template”,
(”myapp/minimal.html”, “Minimal Template”)))
#property
def template(self):
return self.template_string
^ code is from the coactivate.org website, it's not mine to take credit for.
In the template property, you could check if not self.template_string: and set your default path in there.
Edit #1:
Adding Page inheritance.
You can add a parent Page (the Base class) and modify that, then extend any other class with your new Base class. Here's an example:
class BasePage(Page):
"""This is your base Page class. It inherits from Page, and children can extend from this."""
template_string = models.CharField(max_length=255, choices=(
(”myapp/default.html”, “Default Template”),
(”myapp/three_column.html”, “Three Column Template”,
(”myapp/minimal.html”, “Minimal Template”)))
#property
def template(self):
return self.template_string
class CustomPage(BasePage):
"""Your new custom Page."""
#property
def template(self):
"""Overwrite this property."""
return self.template_string
Additionally, you could set the BasePage to be an abstract class so your migrations don't create a database table for BasePage (if it's only used for inheritance)
How to remove the default delete action in Django admin?
Would the following work?
actions = [ ]
This works:
def get_actions(self, request):
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
It's also the recommended way to do this based off Django's documentation below:
Conditionally enabling or disabling actions
In your admin class, define has_delete_permission to return False:
class YourModelAdmin(admin.ModelAdmin):
...
def has_delete_permission(self, request, obj=None):
return False
Then, it will not show delete button, and will not allow you to delete objects in admin interface.
You can disable "delete selected" action site-wide:
from django.contrib.admin import site
site.disable_action('delete_selected')
When you need to include this action, add 'delete_selected' to the action list:
actions = ['delete_selected']
Documentation
If you want to remove all the action:
class UserAdmin(admin.ModelAdmin):
model = User
actions = None
If you want some specific action:
class UserAdmin(admin.ModelAdmin):
model = User
actions = ['name_of_action_you_want_to_keep']
You can globally disable bulk delete action and enable for selected models only.
Documentation from django website
# Globally disable delete selected
admin.site.disable_action('delete_selected')
# This ModelAdmin will not have delete_selected available
class SomeModelAdmin(admin.ModelAdmin):
actions = ['some_other_action']
...
# This one will
class AnotherModelAdmin(admin.ModelAdmin):
actions = ['delete_selected', 'a_third_action']
...
Giving credit to #DawnTCherian, #tschale and #falsetru
I used:
class YourModelAdmin(admin.ModelAdmin):
...
def get_actions(self, request):
actions = super(YourModelAdmin, self).get_actions(request)
try:
del actions['delete_selected']
except KeyError:
pass
return actions
def has_delete_permission(self, request, obj=None):
return False
It removes the delete action from the list view and the delete option from the detail view.
If you are using that model, as a foreign key in some other model.
Then by using PROTECT constraint for that foreign key you can disable deletion for that model in Django admin.
For Example,
class Exam(models.Model):
student = models.ForeignKey(User, on_delete=models.PROTECT)
marks = models.IntegerField(default=0)
By adding PROTECT constraint to User model through the foreign key present in Exam model, I have disabled the power (in Django admin or elsewhere) to delete students (User) who have written exams.
Is it possible to extend a generic view to allow user authentication? I want my view to limit the number of returned results from the model if a user is not logged in.
class CustomGalleryDetailView(DetailView):
def get_queryset(self):
if request.user.is_authenticated():
return Gallery.objects.on_site().is_public()
else:
return Gallery.objects.on_site().is_public()[:5]
This returns NameError global name 'request' is not defined.
The reason I want to extend the generic view is that here I am simply overriding a single of many views used by a 3rd party app in my program, and I want to maintain some consistency with the rest of the views which mainly rely on generic views.
just change it to self.request.user.is_authenticated(), so your class will become:
class CustomGalleryDetailView(DetailView):
def get_queryset(self):
if self.request.user.is_authenticated():
return Gallery.objects.on_site().is_public()
else:
return Gallery.objects.on_site().is_public()[:5]
I have 2 models - for example, Book and Page.
Page has a foreign key to Book.
Each page can be marked as "was_read" (boolean), and I want to prevent deleting pages that were read (in the admin).
In the admin - Page is an inline within Book (I don't want Page to be a standalone model in the admin).
My problem - how can I achieve the behavior that a page that was read won't be deleted?
I'm using Django 1.4 and I tried several options:
Override "delete" to throw a ValidationError - the problem is that the admin doesn't "catch" the ValidationError on delete and you get an error page, so this is not a good option.
Override in the PageAdminInline the method - has_delete_permission - the problem here -it's per type so either I allow to delete all pages or I don't.
Are there any other good options without overriding the html code?
Thanks,
Li
The solution is as follows (no HTML code is required):
In admin file, define the following:
from django.forms.models import BaseInlineFormSet
class PageFormSet(BaseInlineFormSet):
def clean(self):
super(PageFormSet, self).clean()
for form in self.forms:
if not hasattr(form, 'cleaned_data'):
continue
data = form.cleaned_data
curr_instance = form.instance
was_read = curr_instance.was_read
if (data.get('DELETE') and was_read):
raise ValidationError('Error')
class PageInline(admin.TabularInline):
model = Page
formset = PageFormSet
You could disable the delete checkbox UI-wise by creating your own custom
formset for the inline model, and set can_delete to False there. For
example:
from django.forms import models
from django.contrib import admin
class MyInline(models.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(MyInline, self).__init__(*args, **kwargs)
self.can_delete = False
class InlineOptions(admin.StackedInline):
model = InlineModel
formset = MyInline
class MainOptions(admin.ModelAdmin):
model = MainModel
inlines = [InlineOptions]
Another technique is to disable the DELETE checkbox.
This solution has the benefit of giving visual feedback to the user because she will see a grayed-out checkbox.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
if some_criteria_to_prevent_deletion:
form.fields['DELETE'].disabled = True
This code leverages the Field.disabled property added in Django 1.9. As the documentation says, "even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data," so you don't need to add more code to prevent deletion.
In your inline, you can add the flag can_delete=False
EG:
class MyInline(admin.TabularInline):
model = models.mymodel
can_delete = False
I found a very easy solution to quietly avoid unwanted deletion of some inlines. You can just override delete_forms property method.
This works not just on admin, but on regular inlines too.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
#property
def deleted_forms(self):
deleted_forms = super(MyInlineFormSet, self).deleted_forms
for i, form in enumerate(deleted_forms):
# Use form.instance to access object instance if needed
if some_criteria_to_prevent_deletion:
deleted_forms.pop(i)
return deleted_forms