I'm looking to run a specific check, to make sure that Django Admin classes (ModelAdmin, TabularInline etcetera) used within the project (within multiple apps) are using or inheriting from a class (a mixin in this case) - although a system check would fail since the AppRegistry is not yet loaded.
I'm using the below as of current; although this raises that the AppRegistry is not loaded.
from django.contrib.admin.sites import all_sites
from django.core.checks import register, Error
#register()
def check_django_admin_inheritance(app_configs, **kwargs):
errors = []
for admin_site in all_sites:
for model, admin in admin_site._registry.items():
if MyMixin not in admin.__class__.__bases__:
errors.append(
Error('All admin sites should derive from the MyMixin (common.django_admin)',
hint='Inherit from MyMixin or use our custom ModelAdmin (common.django_admin)',
obj=admin, id="id-here")
)
return errors
Is there any other way around this; besides the AppConfig.ready() which would require that I place it in every application - I'm more looking towards a solution that is clean and centralized.
You can simply register your check inside the AppConfig.ready() of some suitable app. Also you write errors.append([Error(...)]) which means you append a list of errors to the list you are supposed to return which will give you an error:
from django.contrib.admin.sites import all_sites
from django.core.checks import register, Error
def check_django_admin_inheritance(app_configs, **kwargs):
errors = []
for admin_site in all_sites:
for model, admin in admin_site._registry.items():
if MyMixin not in admin.__class__.__bases__:
errors.append(
Error('All admin sites should derive from the MyMixin (common.django_admin)',
hint='Inherit from MyMixin or use our custom ModelAdmin (common.django_admin)',
obj=admin, id="id-here")
)
return errors
class MyAppConfig(AppConfig):
...
def ready(self):
register(check_django_admin_inheritance) # Register the check here
I write this in my own app, and the check gives an error message for User and Group of the auth app, so this works as intended.
I want to overwrite create.html and edit.html used for models derived from Wagtails 'PageModel'.
If I understand the docs correctly it should be as simple as specifying the attributes:
class MyAdmin(ModelAdmin):
model = MyPage
create_template_name = "myapp/create.html"
edit_template_name = "myapp/edit.html"
My templates are located at projectroot/templates/myapp. It works fine if my model is a Django model but for a PageModel based model the create view still uses wagtailadmin/pages/create.html. I also tried the other location patterns mentioned in the docs w/o success.
Is it possible to change the edit and create templates for a PageModel? Or do the same limitations as for views apply, i.e. only index.html and inspect.html can be overwritten?
ModelAdmin does not provide create, edit, or delete functionality for Page models, as per the documentation note.
NOTE: modeladmin only provides ‘create’, ‘edit’ and ‘delete’ functionality for non page type models (i.e. models that do not extend wagtailcore.models.Page). If your model is a ‘page type’ model, customising any of the following will not have any effect.
It can be a bit confusing as the ModelAdmin system would seem to work for page models also, but there are some other ways to modify how your page can be edited. These will not be scoped to the ModelAdmin area though.
Option 1 - customise the generated form for your MyPage model
If you only want to customise how the edit page form that gets generated you can modify the base_form_class on your page model.
Wagtail has documentation about how to create a custom page form.
Note: WagtailAdminPageForm extends Django's ModelFormMetaClass
Example
from django import forms
from django.db import models
from wagtail.admin.forms import WagtailAdminPageForm
from wagtail.core.models import Page
class EventPageForm(WagtailAdminPageForm):
# ...
class MyPage(Page):
# ...
base_form_class = MyPageForm
Option 2 - customise the view via hooks
To customise the create & edit views for the normal (e.g. clicking edit page on the Wagtail user bar or explorer) page editing interface, you will need to use Wagtail hooks. Here you have access to the request so you will likely be able to determine if you are in the ModelAdmin area.
Create a file called wagtail_hooks.py in your app folder and provide a hook that will return a custom response (this will need to be rendered by your custom view.).
There are separate hooks for before_create_page and before_edit_page
Example from before_create_page docs below.
from wagtail.core import hooks
from .models import AwesomePage
from .admin_views import edit_awesome_page
#hooks.register('before_create_page')
def before_create_page(request, parent_page, page_class):
# Use a custom create view for the AwesomePage model
if page_class == AwesomePage:
return create_awesome_page(request, parent_page)
```python
I've created a Snippet called "Spotlights," and I'm wondering how I can create a custom endpoint for Snippet data with the Wagtail API. My best guess would be:
api_router.register_endpoint('Spotlights', BaseAPIEndpoint)
Is the first arg there establishing the name of the endpoint, or referencing something?
I've figured it out: just subclass Wagtail's BaseAPIEndpoint. For example:
endpoints.py
from wagtail.api.v2.endpoints import BaseAPIEndpoint
class SpotlightsAPIEndpoint(BaseAPIEndpoint):
...
model = Spotlight
api.py
from .endpoints import SpotlightsAPIEndpoint
api_router.register_endpoint('spotlights', SpotlightsAPIEndpoint)
Plus there are tons of ways to customize it. Just take a look at the endpoints.py file in the Wagtail repo: https://github.com/wagtail/wagtail/blob/master/wagtail/api/v2/endpoints.py
According to Wagtail documentation, the first parameter is the name of the endpoint (eg. pages, images) and this is used in the URL of the endpoint.
The second parameter is the endpoint class that handles the requests.
For example:
api_router.register_endpoint('pages', PagesAPIEndpoint)
api_router.register_endpoint('images', ImagesAPIEndpoint)
api_router.register_endpoint('documents', DocumentsAPIEndpoint)
So, I advise you to make like:
api_router.register_endpoint('spotlights', BaseAPIEndpoint)
Latest Wagtail version - 2.15 +
in your views file, import your model and BaseApiViewSet
from .models import CustomModel
from wagtail.api.v2.views import BaseAPIViewSet
class BusinessLocationViewSet(BaseAPIViewSet):
model = BusinessLocation
body_fields = BaseAPIViewSet.body_fields + ['id', 'field1', 'field2', 'field3', 'field4', etc, etc...]
and in the api.py file in the project folder, import your model and expose it to the api as follows:
from custommodel.views import MyModel
api_router.register_endpoint('custom', MyModel)
and now your model will be accessible from the api/v2/custom endpoint
I needed to modify glls's solution a bit to make it work. To expose the endpoint in api.py:
from .views import CustomViewSet
api_router.register_endpoint("custom", CustomViewSet)
It's also common to add filtering functionality. In views.py:
from wagtail.images.api.v2.views import BaseAPIViewSet, FieldsFilter
from .models import Custom
class CustomViewSet(BaseAPIViewSet):
model = Custom
body_fields = BaseAPIViewSet.body_fields + [
"name"
]
filter_backends = [
FieldsFilter
]
You can now filter the Custom snippet like normal: /api/v2/custom/?name=something
I import
from django.contrib.sites.models import Site in models.py file.
In have this following in admin.py file:
class SitesAdmin(admin.ModelAdmin):
pass
admin.site.unregister(Site)
admin.site.register(Site, SitesAdmin)**
I want to attach validation to the site.domain field in admin.py, How i can accomplish this? please help.
First, specifying an empty ModelAdmin class is unnecessary, the following will work if you don't need to customize the admin:
admin.site.register(Site) # Notice that no ModelAdmin is passed
Now, to your question. You have to create a custom form. Then, you override the clean_domain method of the ModelForm. You can validate any field with the method(s) clean_FOO, where FOO is the field name.
from django import forms
class SiteAdminForm(forms.ModelForm):
def clean_domain(self):
domain = self.cleaned_data.get('domain')
# Custom validation here
return domain
class SiteAdmin(admin.ModelAdmin):
form = SiteAdminForm
admin.site.unregister(Site)
admin.site.register(Site, SiteAdmin)
I'm developing a Django app that will have two administration backends. One for daily use by "normal" users and the default one for more advanced tasks and for the developers.
The application uses some custom permissions but none of the default ones. So I'm currently looking for a way to remove the default permissions, or at least a way to hide them from the "daily" admin backend without large modifications.
UPDATE: Django 1.7 supports the customization of default permissions
Original Answer
The following is valid for Django prior to version 1.7
This is standard functionality of the auth contrib application.
It handles the post_syncdb signal and creates the permissions (the standard 3: add, change, delete, plus any custom ones) for each model; they are stored in the auth_permission table in the database.
So, they will be created each time you run the syncdb management command
You have some choices. None is really elegant, but you can consider:
Dropping the auth contrib app and provide your own authentication backend.
Consequences -> you will lose the admin and other custom apps built on top of the auth User model, but if your application is highly customized that could be an option for you
Overriding the behaviour of the post_syncdb signal inside the auth app (inside \django\contrib\auth\management__init__.py file)
Consequences -> be aware that without the basic permissions the Django admin interface won't be able to work (and maybe other things as well).
Deleting the basic permissions (add, change, delete) for each model inside the auth_permission table (manually, with a script, or whatever).
Consequences -> you will lose the admin again, and you will need to delete them each time you run syncdb.
Building your own Permission application/system (with your own decorators, middlewares, etc..) or extending the existing one.
Consequences -> none, if you build it well - this is one of the cleanest solutions in my opinion.
A final consideration: changing the contrib applications or Django framework itself is never considered a good thing: you could break something and you will have hard times if you will need to upgrade to a newer version of Django.
So, if you want to be as clean as possibile, consider rolling your own permission system, or extending the standard one (django-guardian is a good example of an extension to django permissions). It won't take much effort, and you can build it the way it feels right for you, overcoming the limitations of the standard django permission system. And if you do a good work, you could also consider to open source it to enable other people using/improving your solution =)
I struggled with this same problem for a while and I think I've come up with a clean solution. Here's how you hide the permissions for Django's auth app:
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django import forms
from django.contrib.auth.models import Permission
class MyGroupAdminForm(forms.ModelForm):
class Meta:
model = MyGroup
permissions = forms.ModelMultipleChoiceField(
Permission.objects.exclude(content_type__app_label='auth'),
widget=admin.widgets.FilteredSelectMultiple(_('permissions'), False))
class MyGroupAdmin(admin.ModelAdmin):
form = MyGroupAdminForm
search_fields = ('name',)
ordering = ('name',)
admin.site.unregister(Group)
admin.site.register(MyGroup, MyGroupAdmin)
Of course it can easily be modified to hide whatever permissions you want. Let me know if this works for you.
A new feature introduced in Django 1.7 is the ability to define the default permissions. As stated in the documentation if you set this to empty none of the default permissions will be created.
A working example would be:
class Blar1(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, unique = True, blank = False, null = False, verbose_name= "Name")
class Meta:
default_permissions = ()
ShadowCloud gave a good rundown. Here's a simple way to accomplish your goal.
Add these line in your admin.py:
from django.contrib.auth.models import Permission
admin.site.register(Permission)
You can now add/change/delete permissions in the admin. Remove the unused ones and when you have what you want, go back and remove these two lines from admin.py.
As was mentioned by others, a subsequent syncdb will put everything back.
Built on top of the solution by #pmdarrow, I've come up with a relatively clean solution to patch the Django admin views.
See: https://gist.github.com/vdboor/6280390
It extends the User and Group admin to hide certain permissions.
You can't easily delete those permissions (so that syncdb won't put them back), but you can hide them from the admin interface. The idea is, as described by others, to override the admin forms but you have to do this for both users and groups.
Here is the admin.py with the solution:
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User, Group
from django.contrib.auth.admin import GroupAdmin, UserAdmin
from django.contrib.auth.forms import UserChangeForm
#
# In the models listed below standard permissions "add_model", "change_model"
# and "delete_model" will be created by syncdb, but hidden from admin interface.
# This is convenient in case you use your own set of permissions so the list
# in the admin interface wont be confusing.
# Feel free to add your models here. The first element is the app name (this is
# the directory your app is in) and the second element is the name of your model
# from models.py module of your app (Note: both names must be lowercased).
#
MODELS_TO_HIDE_STD_PERMISSIONS = (
("myapp", "mymodel"),
)
def _get_corrected_permissions():
perms = Permission.objects.all()
for app_name, model_name in MODELS_TO_HIDE_STD_PERMISSIONS:
perms = perms.exclude(content_type__app_label=app_name, codename='add_%s' % model_name)
perms = perms.exclude(content_type__app_label=app_name, codename='change_%s' % model_name)
perms = perms.exclude(content_type__app_label=app_name, codename='delete_%s' % model_name)
return perms
class MyGroupAdminForm(forms.ModelForm):
class Meta:
model = Group
permissions = forms.ModelMultipleChoiceField(
_get_corrected_permissions(),
widget=admin.widgets.FilteredSelectMultiple(('permissions'), False),
help_text = 'Hold down "Control", or "Command" on a Mac, to select more than one.'
)
class MyGroupAdmin(GroupAdmin):
form = MyGroupAdminForm
class MyUserChangeForm(UserChangeForm):
user_permissions = forms.ModelMultipleChoiceField(
_get_corrected_permissions(),
widget=admin.widgets.FilteredSelectMultiple(('user_permissions'), False),
help_text = 'Hold down "Control", or "Command" on a Mac, to select more than one.'
)
class MyUserAdmin(UserAdmin):
form = MyUserChangeForm
admin.site.unregister(Group)
admin.site.register(Group, MyGroupAdmin)
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
If you are creating your own user management backend and only want to show your custom permissions you can filter out the default permissions by excluding permission with a name that starts with "Can".
WARNING:
You must remember not to name your permissions starting with "Can"!!!!
If they decide to change the naming convention this might not work.
With credit to pmdarrow this is how I did this in my project:
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.models import Permission
from django.contrib import admin
class UserEditForm(UserChangeForm):
class Meta:
model = User
exclude = (
'last_login',
'is_superuser',
'is_staff',
'date_joined',
)
user_permissions = forms.ModelMultipleChoiceField(
Permission.objects.exclude(name__startswith='Can'),
widget=admin.widgets.FilteredSelectMultiple(_('permissions'), False))
If you want to prevent Django from creating permissions, you can block the signals from being sent.
If you put this into a management/init.py in any app, it will bind to the signal handler before the auth framework has a chance (taking advantage of the dispatch_uid debouncing).
from django.db.models import signals
def do_nothing(*args, **kwargs):
pass
signals.post_syncdb.connect(do_nothing, dispatch_uid="django.contrib.auth.management.create_permissions")
signals.post_syncdb.connect(do_nothing, dispatch_uid="django.contrib.auth.management.create_superuser")