I want to create a healthcheck endpoint for my django admin panel.
I want to register it via admin.site.register_view (I am using the adminplus package) but I can't figure out how to made it accessible to the public, without the need to authenticate first.
Any ideas?
So I ended up overriding def has_permission(self, request) in subclass of AdminPlusMixin:
from adminplus.sites import AdminPlusMixin
class MyAdmin(AdminPlusMixin, AdminSite):
def has_permission(self, request):
if request.resolver_match.url_name == 'admin-healthcheck':
return True
return super().has_permission(request)
I believe that package is outdated, instead you could do something like the following:
Created a proxy model in models.py such as:
class Proxy(models.Model):
id = models.BigAutoField(db_column='id', primary_key=True)
def str(self):
return "<Label: id: %d>" % self.id
class Meta:
managed = False
verbose_name_plural = 'proxies'
db_table = 'proxy'
ordering = ('id',)
Which is just a mysql view that a created from am existing table
create view proxy
as select id
from samples
LIMIT 10;
And finally in admin.py
#admin.register(Proxy)
class LabelAdmin(admin.ModelAdmin):
change_list_template = 'label_view.html'
def changelist_view(self, request, extra_context=None):
...
return render(request, "label_view.html", context)
This way it will show in the admin panel, inside the app you're working on.
Probably what you have is a function in your views.py, in this case you should replace that function content where the "..." are in the LabelAdmin class.
Related
hello i had done some modification in models and get the User.username and inserted them in a user_list and in multiselectfield(choices=user_list)
is was working fine but in the case as i made/create a new user
and i go to the application to select multiple users, my newly made user is not showing there
until i restart the django server
my model is like that:
class Video(models.Model):
listo = []
user = User.objects.all()
for i in user:
a = (str(i), str(i))
listo.append(a)
print("this is listo", listo)
caption = models.CharField(max_length=100)
video = models.FileField(upload_to="video/%y",null=True,blank=True)
show_to = MultiSelectField(choices=User.username)
# show_to = models.ForeignKey(User, on_delete=models.CASCADE)
url_video= models.URLField(blank=True)
def __str__(self):
return self.caption
the thing i want is to instantly show the newly made user in the django application so i could not restart the server again and again.
thanks in advance!
Your defined Model choices are set once when your server start or restart, So your choices not updated after adding users.
You create a ModelForm for your model and set choices for model field in init method of form. When you open form, every time call constructor of your form and set choices.
from django import forms
class VideoForm(forms.ModelForm):
...
...
def __init__(self, *args, **kwargs):
super(VideoForm, self).__init__(*args, **kwargs)
self.fields['show_to'].choices = self.get_dynamic_choices()
def get_dynamic_choices(self):
choices = [(obj.id, obj.username) for obj in User.objects.all()]
return choices
In an application I'm building, I've created a series of custom model managers and querysets to have a higher level api.
The problem comes when I execute an admin action. The queryset passed to it seems to be a generic one, and I would like to have access to my custom queryset to be able to use the filtering functions I created in it.
This is the action:
def mark_payment_as_sent_action():
''' Admin action to mark payment as sent '''
def mark_payment_as_sent(modeladmin, request, queryset):
# #####################################################################
# This is what I currently do
payments = queryset.filter(status=models.Payment.S_PENDING)
# This is what I want to do
payments = queryset.pending()
# #####################################################################
# Do stuff with filtered payments
return HttpResponseRedirect("...")
mark_payment_as_sent.short_description = "Mark as sent"
return mark_payment_as_sent
These are the custom model manager an query set:
class PaymentQuerySet(models.query.QuerySet):
def pending(self):
return self.filter(status=self.model.S_PENDING)
class PaymentManager(models.Manager):
use_for_related_fields = True
def get_query_set(self):
return PaymentQuerySet(self.model)
def pending(self, *args, **kwargs):
return self.get_query_set().pending(*args, **kwargs)
And finally the model and admin classes:
class Payment(models.Model):
status = models.CharField(
max_length=25,
choices=((S_PENDING, 'Pending'), ...)
)
objects = managers.PaymentManager()
#admin.register(models.Payment)
class PaymentsAdmin(admin.ModelAdmin):
actions = (
admin_actions.mark_payment_as_sent_action(),
)
Any hint on how can I tell Django to use my queryset when calling an admin action?
Thanks a lot.
As noamk noted, the problem was the method name. Django renamed the get_query_set method to get_queryset.
Now it's working petectly.
class PaymentQuerySet(models.query.QuerySet):
def pending(self):
return self.filter(status=self.model.S_PENDING)
class PaymentManager(models.Manager):
use_for_related_fields = True
def get_queryset(self):
return PaymentQuerySet(self.model)
def pending(self):
return self.get_queryset().pending()
I'm need to add an additional filter property (in the background) to a django-filter request.
My Model:
class Event(models.Model):
name=models.CharField(max_length=254)
location=models.ForeignKey(Place)
invited_user=models.ManyToManyField(User,null=True, blank=True)
With a filter those entries with the same location can be filtered. This is working.
Further on I have to exclude all those entries where the invited_user is not the request.user (choosing this filter property is only possible if the user has permissions).
Is this possible with django-filter, and if yes how?
My filter Class:
import django_filters
from models import Event
class EventFilter(django_filters.FilterSet):
class Meta:
model = Event
fields = ['location']
My work is based on: How do I filter tables with Django generic views?
you can access the request object in FilterSet.qs property.
class EventFilter(django_filters.FilterSet):
class Meta:
model = Event
fields = ['location']
#property
def qs(self):
queryset=super(EventFilter, self).qs
if request.user.has_perm("app_label.has_permission"):
return queryset.exclude(invited_user!=self.request.user)
return queryset
docs https://rpkilby.github.io/django-filter/guide/usage.html#filtering-the-primary-qs
I think in your case you could do it by modifying the queryset in the view, where you should be able to access request.user. Therefore you wouldn't need to dig deep into django-filter,
In my case, when using dango_filters FilterView along with crispy forms to render the form, I wanted to hide fields from the form, along with additional filtering as you described, so I overrode get() for the FilterView, restricted the queryset to the user, and used crispy form's layout editing to pop the unwanted fields from the filter form:
def get(self, request, *args, **kwargs):
"""
original code from django-filters.views.BaseFilterView - added admin check
"""
filterset_class = self.get_filterset_class()
self.filterset = self.get_filterset(filterset_class)
self.object_list = self.filterset.qs
# If not admin, restrict to assessor's own centre and clients
if not request.user.get_profile().is_admin:
self.object_list = self.object_list.filter(attendee__assessor=request.user)
self.filterset.form.helper.layout[0].pop(2) # centres filter
self.filterset.form.helper.layout[0].pop(1) # assessors filter
context = self.get_context_data(filter=self.filterset,
object_list=self.object_list)
return self.render_to_response(context)
Try this:
class EventListView(BaseFilterView):
...
def get_filterset(self, *args, **kwargs):
fs = super().get_filterset(*args, **kwargs)
fs.filters['location'].field.queryset = fs.filters['location'].field.queryset.filter(user=self.request.user)
return fs
So I'm having a bit of trouble with trying to create a model that will define dynamic proxy models that manage a related model in the admin site. I know that sentence was confusing, so I'll just share my code instead.
models.py
class Cateogry(models.Model):
name = models.CharField(...)
class Tag(models.Model):
name = models.CharField(...)
category = models.ForeignKey(Cateogry)
What I want to achieve is that in the admin site, instead of having one ModelAdmin for the Tag model, for each category I will have a modeladmin for all related tags. I have achieved this using this answer. Say I have a category named A:
def create_modeladmin(modeladmin, model, name = None):
class Meta:
proxy = True
app_label = model._meta.app_label
attrs = {'__module__': '', 'Meta': Meta}
newmodel = type(name, (model,), attrs)
admin.site.register(newmodel, modeladmin)
return modeladmin
class CatA(TagAdmin):
def queryset(self, request):
qs = super(CatA, self).queryset(request)
return qs.filter(cateogry = Cateogry.objects.filter(name='A'))
create_modeladmin(CatA, name='CategoryAtags', model=Tag)
But this is not good enough, because obviously I still need to manually subclass the TagAdmin model and then run create_modeladmin. What I need to do, is loop over all Category objects, for each one create a dynamic subclass for Tagadmin (named after the category), then create a dynamic proxy model from that, and this is where my head starts spinning.
for cat in Category.objects.all():
NewSubClass = #somehow create subclass of TagAdmin, the name should be '<cat.name>Admin' instead of NewSubClass
create_modeladmin(NewSubClass, name=cat.name, model=Tag)
Any guidance or help would be much appreciated
Dynamic ModelAdmins don't work well together with the way admin registeres models.
I suggest to create subviews in the CategoryAdmin.
from django.conf.urls import patterns, url
from django.contrib import admin
from django.contrib.admin.options import csrf_protect_m
from django.contrib.admin.util import unquote
from django.core.urlresolvers import reverse
from demo_project.demo.models import Category, Tag
class TagAdmin(admin.ModelAdmin):
# as long as the CategoryTagAdmin class has no custom change_list template
# there needs to be a default admin for Tags
pass
admin.site.register(Tag, TagAdmin)
class CategoryTagAdmin(admin.ModelAdmin):
""" A ModelAdmin invoked by a CategoryAdmin"""
read_only_fields = ('category',)
def __init__(self, model, admin_site, category_admin, category_id):
self.model = model
self.admin_site = admin_site
self.category_admin = category_admin
self.category_id = category_id
super(CategoryTagAdmin, self).__init__(model, admin_site)
def queryset(self, request):
return super(CategoryTagAdmin, self).queryset(request).filter(category=self.category_id)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'tag_changelist_link')
def tag_changelist_link(self, obj):
info = self.model._meta.app_label, self.model._meta.module_name
return '<a href="%s" >Tags</a>' % reverse('admin:%s_%s_taglist' % info, args=(obj.id,))
tag_changelist_link.allow_tags = True
tag_changelist_link.short_description = 'Tags'
#csrf_protect_m
def tag_changelist(self, request, *args, **kwargs):
obj_id = unquote(args[0])
info = self.model._meta.app_label, self.model._meta.module_name
category = self.get_object(request, obj_id)
tag_admin = CategoryTagAdmin(Tag, self.admin_site, self, category_id=obj_id )
extra_context = {
'parent': {
'has_change_permission': self.has_change_permission(request, obj_id),
'opts': self.model._meta,
'object': category,
},
}
return tag_admin.changelist_view(request, extra_context)
def get_urls(self):
info = self.model._meta.app_label, self.model._meta.module_name
urls= patterns('',
url(r'^(.+)/tags/$', self.admin_site.admin_view(self.tag_changelist), name='%s_%s_taglist' % info )
)
return urls + super(CategoryAdmin, self).get_urls()
admin.site.register(Category, CategoryAdmin)
The items in the categories changelist have an extra column with a link made by the tag_changelist_link pointing to the CategoryAdmin.tag_changelist. This method creates a CategoryTagAdmin instance with some extras and returns its changelist_view.
This way you have a filtered tag changelist on every category. To fix the breadcrumbs of the tag_changelist view you need to set the CategoryTagAdmin.change_list_template to a own template that {% extends 'admin/change_list.html' %} and overwrites the {% block breadcrumbs %}. That is where you will need the parent variable from the extra_context to create the correct urls.
If you plan to implement a tag_changeview and tag_addview method you need to make sure that the links rendered in variouse admin templates point to the right url (e.g. calling the change_view with a form_url as paramter).
A save_model method on the CategoryTagAdmin can set the default category when adding new tags.
def save_model(self, request, obj, form, change):
obj.category_id = self.category_id
super(CategoryTagAdmin, self).__init__(request, obj, form, change)
If you still want to stick to the apache restart aproach ... Yes you can restart Django. It depends on how you are deploying the instance.
On an apache you can touch the wsgi file that will reload the instance os.utime(path/to/wsgi.py.
When using uwsgi you can use uwsgi.reload().
You can check the source code of Rosetta how they are restarting the instance after the save translations (views.py).
So I found a half-solution.
def create_subclass(baseclass, name):
class Meta:
app_label = 'fun'
attrs = {'__module__': '', 'Meta': Meta, 'cat': name }
newsub = type(name, (baseclass,), attrs)
return newsub
class TagAdmin(admin.ModelAdmin):
list_display = ('name', 'category')
def get_queryset(self, request):
return Tag.objects.filter(category = Category.objects.filter(name=self.cat))
for cat in Category.objects.all():
newsub = create_subclass(TagAdmin, str(cat.name))
create_modeladmin(newsub, model=Tag, name=str(cat.name))
It's working. But every time you add a new category, you need to refresh the server before it shows up (because admin.py is evaluated at runtime). Does anyone know a decent solution to this?
I have the following model and admin defined in djando 1.5. This is a many-to-many relationship between subnet and vlan. I use the related_name option in the ManyToMany field to be able to get the collection of vlan from the related subnet object. Adding subnet to vlans from the admin works well. However when I try to add an horizontal_filer to the subnet admin in order to add van to its vlan set I get an error saying that the vlans attribute doesn't exist. I'm using subnet object in some view and I can access the vlans attribute just right.
What am I doing wrong here ? I've seen similar post but I couldn't adapt any of the proposed solution with success.
Thanks for your help
model.py
from django.db import models
class Subnet(models.Model):
networkAddress = models.CharField(max_length=15)
size = models.IntegerField()
def __unicode__(self):
return "%s/%s" % (self.networkAddress, self.size)
class IpAddress(models.Model):
ipAddress = models.CharField(max_length=15)
subnet = models.ForeignKey(Subnet)
def __unicode__(self):
return "%s" % (self.ipAddress)
class Vlan(models.Model):
number = models.IntegerField()
description = models.CharField(max_length=150)
subnets = models.ManyToManyField(Subnet, related_name='vlans', blank=True)
def __unicode__(self):
return "VLAN %s (%s)" % (self.number, self.description)
admin.py
from network.models import Subnet, IpAddress, Vlan
from django.contrib import admin
class SubnetAdmin(admin.ModelAdmin):
filter_horizontal = ('vlans',)
admin.site.register(Subnet, SubnetAdmin)
admin.site.register(IpAddress)
admin.site.register(Vlan)
and the error I get
Request Method: GET
Request URL: http://127.0.0.1:8000/admin/
Django Version: 1.5.2
Exception Type: ImproperlyConfigured
Exception Value:
'SubnetAdmin.filter_horizontal' refers to field 'vlans' that is missing from model 'network.Subnet'.
Apparently this is an 8 year old feature request. There is django-admin-extend. Or you could just throw something like this in there:
from django.contrib import admin as admin_module
class SiteForm(ModelForm):
user_profiles = forms.ModelMultipleChoiceField(
label='Users granted access',
queryset=UserProfile.objects.all(),
required=False,
help_text='Admin users (who can access everything) not listed separately',
widget=admin_module.widgets.FilteredSelectMultiple('user profiles', False))
class SiteAdmin(admin_module.ModelAdmin):
fields = ('user_profiles',)
def save_model(self, request, obj, form, change):
# save without m2m field (can't save them until obj has id)
super(SiteAdmin, self).save_model(request, obj, form, change)
# if that worked, deal with m2m field
obj.user_profiles.clear()
for user_profile in form.cleaned_data['user_profiles']:
obj.user_profiles.add(user_profile)
def get_form(self, request, obj=None, **kwargs):
if obj:
self.form.base_fields['user_profiles'].initial = [ o.pk for o in obj.userprofile_set.all() ]
else:
self.form.base_fields['user_profiles'].initial = []
return super(SiteAdmin, self).get_form(request, obj, **kwargs)
It should give you a filter_horizontal when you specify it in the fields tuple.
I have created a public gist that covers this specific issue.
https://gist.github.com/Wtower/0b181cc06f816e4feac14e7c0aa2e9d0
The general idea is to use that specific base form class in order to define a 'reverse' m2m field for the form that would otherwise not include it. Then easily override the form in the admin class.
Although the code is not quite complicated, the gist code is somehow long to include within the answer so apologies for that.