Suppose I have a model like this:
class Foo(models.Model):
name = models.CharField(max_length=50)
year = models.IntegerField(max_length=4)
some_value = models.IntegerField(default=0)
and
class Bar(models.Model)
name = models.CharField(max_length=50)
foo = models.ForeignKey(Foo)
Then I register my models in django AdminSite using FooAdmin class:
class FooAdmin(admin.ModelAdmin):
list_display = ['name', 'year', 'some_value']
It works fine, but for some reason I need to add two buttons for each Foo row in my admin site. Let's say I'd call them 'Related_button' and 'Action_button'
I need those buttons to be in each row and to behave as follows:
-when user clicks on 'Related_button', he is redirected to django admin site where all Bars related with particullar Foo object are listed
-when user clicks on 'Action_button', a field some_value in object of class Foo is set to a custom value, let's say 15. But before that, confirm popup (Are you sure? y/n) should appear.
How to do that? I figured out that I could do something like:
def button(self, obj):
return '''<input type="button" value="button" />'''
button.short_description = 'Action'
button.allow_tags = True
list_display = ['name', 'year', 'some_value', 'action']
in my FooAdmin which causes a button to appear in each row. But how can I set an action for that button?
The list page is a form.
<form id="changelist-form" action="" method="post">
Adding buttons to it will submit the form. To do something with the formdata you have to create a custom admin form. It will be dirty because it's a fromset containing all displayed rows, and not just your single row. I guess it can be done, but there are better ways:
Most of the time Admin Actions will do just fine. Admin actions look like this:
def rename_action(modeladmin, request, queryset):
queryset.update(name='Ni')
make_published.short_description = "Rename selected objects to 'Ni'"
class FooAdmin(admin.ModelAdmin):
actions = [rename_action, ...]
But if you want more flexibility than an Admin Action, feel free to write custom views for the admin. The admin is powered by Django itself, and you can write custom views that hook into the authentication system, check permissions and do whatever else they need to do. It goes like this:
class Foo(models.Model):
...
def my_action_link(self, obj):
return 'Action name' %obj.id
my_action_link.short_description = 'My action'
my_action_link.allow_tags = True
Now my action is a link that points to some custom url and custom admin view. Create a custom admin view:
class FooAdmin(admin.ModelAdmin):
...
list_display = ['name', 'my_action_link', ...]
def get_urls(self):
urls = super(FooAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_custom_action/(?P<pk>\d+)/$', self.my_view)
)
return my_urls + urls
#permission_required('foo.can_change')
def my_view(self, request):
obj = get_object_or_404(Foo, pk=pk)
obj.do_something()
# Redirect back to the change list. Or something else?
# You could add some modelform to this view. :)
I didn't test this code. But I hope you get the idea. Happy hacking!
[EDITED]: Linked reference Django 1.6 URLs not working anymore. Added Django 1.11 URLs
Add custom js to admin class:
class FooAdmin(admin.ModelAdmin):
list_display = ['name', 'year', 'some_value']
class Media:
js = ("/media/javascript/yourjs.js",)
Related
Django 1.11. I have 2 models, Foo and Bar:
class Foo(models.Model):
name = models.CharField()
class Bar(models.Model):
name = models.CharField()
foo = models.ForeignKey(Foo)
In the Foo detail page in the Django admin, I list all child Bars underneath the Foo details:
#admin.register(Foo)
class FooAdmin(admin.ModelAdmin):
def bars(self):
html = ''
bs = self.bar_set.all()
for b in bs:
html += '%s<br>' % (reverse('admin:app_bar_change', args=(b.id,)), b.name)
html += '<a href="%s">Add a bar</button>' % (reverse('admin:app_bar_add'))
return html
bars.allow_tags = True
fields = ('name', bars)
readonly_fields = (bars,)
As you can see, I also add a button to add a new Bar. This works, but what I want is to automatically prepopulate the Foo dropdown when adding a new Bar. I.e. I want to add a Bar to the currently open Foo. How can I do this in Django?
For this django allows you to include inlines in your admin class. For example the following would display the related objects in a table without displaying the form to change values for the name field;
class BarInline(admin.TabularInline):
model = Bar
fields = (
'name'
)
readonly_fields = (
'name'
)
show_change_link = True
#admin.register(Foo)
class FooAdmin(admin.ModelAdmin):
fields = ('name', )
inlines = [
BarInline,
]
By setting show_change_link you can display a change link for the inline object which I believe is what you're really looking for here.
Django provides two subclasses of InlineModelAdmin and they are:
TabularInline
StackedInline
The difference between these two is merely the template used to render them.
If these inlines don't meet your needs, you could just create your own to use a custom template.
from django.contrib.admin.options import InlineModelAdmin
class ListInlineAdmin(InlineModelAdmin):
template = 'admin/edit_inline/list.html'
Then in the new list.html template you can get it to display the object name & change URL as you want.
I found the solution in this SO answer. Pre-populated fields can be specified in the URL's query string. So my code now looks like this:
if self.id:
html += 'Add a bar' % (reverse('admin:app_bar_add'), self.id)
Edit: I need to check first that the instance has an id, i.e. that it has already been saved.
I have a model Item that has a foreign key pointing to Category.
From the Category admin page, I would like to be able to choose existing Item objects and modify them too.
class Item(models.Model):
name = models.CharField(max_length=63)
category = models.ForeignKey('Category', null=True, related_name="items")
class Category(models.Model):
name = models.CharField(max_length=63)
I have tried setting up the admin this way but it's simply displaying blank inlines and no magnifying glass or select dropdown to choose from existing Item instances.
class ItemInline(admin.StackedInline):
model = Item
allow_add = True
raw_id_fields = ('category',)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name']
inlines = [
ItemInline
]
# also tried putting raw_id_fields = ('items',) here
# but it prompts an error saying 'CategoryAdmin.raw_id_fields' refers to field 'items' that is missing from model 'Category'.
It seems you've misunderstood how the link works. Django doesn't offer a selection for reverse foreign keys. From the Item admin you could select the Category like that. But not the other way around.
One workaround would be to use a project that adds custom widgets such as Django Tags Input which adds a tag-like input field to your admin.
In this case the configuration would look something like this:
settings.py
INSTALLED_APPS = (
# ... your other installed apps
'tags_input',
)
TAGS_INPUT_MAPPINGS = {
'your_app.Item': {
'field': 'name',
},
}
admin.py
from tags_input import admin as tags_input_admin
class CategoryAdmin(tags_input_admin.TagsInputAdmin):
list_display = ['name']
urls.py
from django.conf import urls
urlpatterns = patterns('',
url(r'^tags_input/', include('tags_input.urls', namespace='tags_input')),
# ... other urls ...
)
PS: To easily create a fully functioning Django admin config try the Django Admin Generator package.
Disclaimer: The linked projects are ones I wrote.
My models.py looks like this:
class Person(models.Model):
Name = models.CharField(max_length=100)
class Lecture(models.Model):
Speaker = model.ForeignKey(Person)
Topic = models.CharField(max_length=100)
Choices = ((1,"Upcoming"),(2,"In Progress",),(3,"Completed"))
Status = models.SmallIntegerField(choices=Choices, default=1, max_length=1)
My admin.py looks like this:
class LectureAdmin(admin.ModelAdmin):
def get_queryset(self):
return Lecture.objects.exclude(Status='Completed')
So my change list view in the django admin for the Lecture model shows only Lectures in "Upcoming" and "In Progress" status. This works fine.
Now I need to get the URL for the list of all lectures to be passed as a view somewhere else.The standard way of doing this in the django admin is by reversing the URL, so I do this:
urlresolvers.reverse('admin:%s_%s_changelist' % (app_label, model_name))
However, when I do this,I get the the filtered Queryset with Lectures in "Completed" state missing.How do I construct a url reverse function to get entire Lecture queryset and not the filtered queryset?
Here's a workaround, looks ugly, I understand.
Add all GET parameter to the changelist url:
url = urlresolvers.reverse('admin:%s_%s_changelist' % (app_label, model_name))
url += '?all'
Call get_queryset() on super(), exclude Completed status only if there is no all in request.GET:
class LectureAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(LectureAdmin, self).get_queryset(request)
if 'all' not in request.GET:
qs = qs.exclude(Status='Completed')
return qs
UPD (applying other filters from request.GET):
from xadmin.plugin.related import RELATE_PREFIX # or just set RELATE_PREFIX = '_rel_'
qs = qs.filter(**{key[len(RELATE_PREFIX):]: value
for key, value in request.GET.iteritems()
if key.startswith(RELATE_PREFIX)})
** unpacks the dictionary into keyword arguments.
Hope it works for you.
get_queryset() is the basic queryset used in admin listing, thus you wo'nt be able to get all the records if you override it this way.
Possible solutions:
use filters ( https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.filter_vertical ) to exclude unwanted records (these with Status='Completed'
or
create proxy model for Lecture, register it in admin and use modified get_queryset() in given listing. Proxy model is required because each model can have registered only single AdminModel class
models.py
class IncompletedLecture(Lecture):
class Meta:
proxy = True
admin.py
class IncompletedAdmin(admin.ModelAdmin):
def get_queryset():
return Lecture.query.exclude(Status='Completed')
admin.site.register(IncompletedLecture, IncompletedAdmin)
I would like to implement a very simple feature with the django admin panel but I couldn't find the right way to achieve this so far:
model.py
class Author(models.Model):
name = models.CharField()
class Books(models.Model):
title = models.CharField()
author = models.ForeignKey(Author)
admin.py
class AuthorAdmin(admin.ModelAdmin):
pass
admin.site.register(Author, AuthorAdmin)
How can I add a hyperlink to every item (Author) in the authors' list overview that links to a view showing all books of the specific author? For Example:
J.K. Rowling (books)
J.R.R. Tolkien (books)
where books is a hyperlink to a site showing all books of the author.
You are looking for a ModelAdmin.list_filter.
Set list_filter to activate filters in the right sidebar of the change list page of the admin. A listfilter can be a field name, where the specified field should be either a BooleanField, CharField, DateField, DateTimeField, IntegerField, ForeignKey or ManyToManyField, for example:
# Add a list filter author to BookAdmin.
# Now you can filter books by author.
class BookAdmin(ModelAdmin):
list_filter = ('author', )
Now you can use #Wolph suggestion to add a link in the Author list_display. This link points to the book list filtered by author:
# Add hyperlinks to AuthorAdmin.
# Now you can jump to the book list filtered by autor.
class AuthorAdmin(admin.ModelAdmin):
def authors(self):
return '%s' % (self.author_id, self.author)
authors.allow_tags = True
ALTERNATIVE. To save a click you can also point to the change view of a book directly:
class Books(models.Model):
title = models.CharField()
author = models.ForeignKey(Author)
def get_admin_url(self):
return "/admin/appname/books/%d/" %self.id
class BookAdmin(admin.ModelAdmin):
def authors(self):
html = ""
for obj in Books.objects.filter(author__id_exact=self.id):
html += '<p>%s</p>' %(obj.get_admin_url(), obj.title)
return html
authors.allow_tags = True
list_display = ['title', authors]
Disclaimer: Not tested, but if you fix the typo's it'll work! :)
Note that these links can also be injected at other points in the admin. When you add it to a widget, you can go from change view to change view.
Look into implementing an InlineModelAdmin object.
# admin.py
class BooksInline(admin.TabularInline):
model = Books
class AuthorAdmin(admin.ModelAdmin):
inlines = [BooksInline]
Edited answer in response to OP's comment:
class AuthorAdmin(admin.ModelAdmin):
list_display = ['name', 'books_published']
def books_published(self, obj):
redirect_url = reverse('admin:books_changelist')
extra = "?author__id__exact=%d" % (obj.id)
return "<a href='%s'>Books by author</a>" % (redirect_url + extra)
books_published.allow_tags = True
It's not as simple as you might think actually, possible but not trivial since there is not that much documentation of this feature :)
What you need is a custom column which is generated by a function which gives you a link to the Django admin with a given filter. Something like this (from the top of my head so not tested) should do the trick:
class AuthorAdmin(admin.ModelAdmin):
def authors(self):
return '%s' % (self.author_id, self.author)
authors.allow_html = True
list_display = ['title', authors]
I am new at Django and I have tried my best to understand it but I still have a long way to go.
I am working on a project in which I have to make a webpage where I have to display data from these classes :
class team(models.Model):
team_name = models.CharField(max_length = 40)
def __unicode__(self):
return self.team_name
class metric(models.Model):
team = models.ForeignKey(team)
metric_name = models.CharField(max_length = 40)
def __unicode__(self):
return self.metric_name
class members(models.Model):
metric = models.ForeignKey(metric)
member_ID = models.CharField(max_length = 40)
member_name = models.CharField(max_length = 40, null=True, blank=True)
def __unicode__(self):
return self.member_ID
Furthermore I want this data to be editable as well. Meaning there should be an Edit button which once pressed would enable the user to edit these fields and once he saves it the database get's updated automatically.
Now as far as I have understood. I would need to display this data using tables of some sort and then once the user clicks on the Edit button I would have to change the Tables into form so he can edit the data ? and then update the database once he clicks on save.
I am not sure how to do all this in Django.
If anyone could please refer to me step by step on how to proceed and maybe refer to some References It would help me a lot
Thanks :)
UPDATE: I forgot to mention it before. I have already created a page where user selects team_name.. Then he is redirected to another page where I want to show the table for that specific team. And I also want that page to be editable. If I use modelforms then I won't be able to access single object of my team model.
UPDATE2: I am still stuck at this. I can't understand on how to display just few elements and variables of a model class rather then all of it. Apart from that I can't understand how to create a Form that can take input from the user on which object of the database to edit and then display that certain objects to be editable.
Related to the questioneers-comments here and here.
First of all, you should write your class-names capitalized. Please check the documentation.
You create Forms for a queryset like this.
forms.py
from django.forms import ModelForm
class MembersForm(ModelForm):
class Meta:
model = Members
urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('yourapp.views',
url(r'^$',
# /
view = 'index',
name = 'team-index',
),
url(r'^(?P<lookup_team>[-\w]+)/$',
# .../team-extreme/
view = 'update',
name = 'update-members',
),
)
views.py (of your application)
from django.http import HttpResponse
from django.template.context import RequestContext
from django.shortcuts import render_to_response, reverse, redirect
from yourapp.models import Members
from yourapp.forms import MembersForm
def index(request):
queryset = Members.objects.all()
template = 'yourapp/index.html'
kwvars = {
'data': queryset,
}
return render_to_response(template, kwvars, RequestContext(request))
def update(request, lookup_team):
"""change members being in a special team"""
queryset = Members.objects.filter(metric__team__team_name=lookup_team)
if request.POST:
form = MemberForm(request.POST, instance=queryset)
if form.is_valid():
form.save()
return redirect(reverse('team-index'))
else:
form = MemberForm(instance=queryset)
template = 'yourapp/update.html'
kwvars = {
'form': form,
}
return render_to_response(template, kwvars, RequestContext(request))
Hope this helps. If you got any questions hit me a comment.
If you want to implement it as dynamically as you describe, then this is impossible by Django alone; Django can serve you the table and the forms, but a dynamic change without reloading the page (which I'm guessing is what you really want) needs some client side code, JavaScript.
But about showing the data and the forms: use Django's class based generic views.
The ListView and DetailsView should get you started with showing the data from the models.
The ModelForm in combination with the appropriate generic editing views should get you going with editing the data.
These views will, per default, return the data in some - generally useful - format and can be customized by using your own template. I suggest you read up on the links I provided you and work from there. That should get you going.