django admin edit model select/prefetch_related? - django

I have a Django website, with model Event, lets say:
class Event(models.Model):
home = models.ForeignKey('Team', related_name='%(class)s_home')
away = models.ForeignKey('Team', related_name='%(class)s_away')
...
class Team(models.Model):
name = models.CharField("team's name", max_length=100)
Using ForeignKeys for this was a bad idea, but anyway, how to make this usable in Django Admin page?
In admin edit event page, a ton of foreign keys is fetched for this:
http://127.0.0.1:8000/admin/event/event/116255/
It produces tons of selects like:
SELECT "event_team"."id", "event_team"."name" FROM "event_team" WHERE "event_team"."id" = 346;
and page dies. I was playing with these:
class EventAdmin(admin.ModelAdmin):
list_display = ('id', 'home', 'away', 'date_game', 'sport', 'result')
search_fields = ['home__name', 'away__name']
list_select_related = (
'home', 'away', 'league', 'sport', ...
)
def get_queryset(self, request):
return super(EventAdmin, self).get_queryset(request).select_related(*self.list_select_related)
admin.site.register(Event, EventAdmin)
But no luck.

The simplest, quickest way
It would be to add raw_id_fields on your ModelAdmin (Django ModelAdmin.raw_id_fields documentation) :
class EventAdmin(admin.ModelAdmin):
raw_id_fields = ("home", "away")
It would result in a inputText with the FK field ids in such as :
.
Loading will be fast as it won't populate a select list with ALL the teams.
You'll have the Django admin change_view of the Team ModelAdmin to select the teams, thanks to the browse icon.
A nicer way ?
A lot more elegant on the UX side of things, it requires you to know part of the name of the team: using an ajax autocomplete widget to represent your field.
You could use for example Django Autocomplete Light (DAL) quick tutorial by having a custom form for your admin and a autocompleteModelSelect2 for your home and away fields (with 2 differents QS in the ajax view).
It will produce a field looking like:
.
The tutorial of this link have all you need!
Or you can chose another third party plugin or build your own field/widget to produce a similar result.

I think #ppython's answer is the simplest and works perfectly but I ended up using autocomplete_fields instead of raw_id_fields. Achieving a more friendly approach, it has been available since Django 2.0.
Following the answer, it'll be something like this:
class EventAdmin(admin.ModelAdmin):
autocomplete_fields = ['home', 'away']

I found the problem, it was my mistake and I did not even mention it in question :(
So this is my model, but I did not mention important part if it:
class Event(models.Model):
home = models.ForeignKey('Team', related_name='%(class)s_home')
away = models.ForeignKey('Team', related_name='%(class)s_away')
merged = models.ForeignKey('Event', null='True', blank='True')
def __unicode__(self):
return str(self.id) + ": " + self.home.name + " - " + self.away.name
Problem was not with home or away, but with merged field, that fetched self.home.name and self.away.name for each event.
Replaced with
def __unicode__(self):
return 'Event id: {}'.format(self.id)
and added merged to list_select_related
fixed my problem. Thanks for help and sorry for incomplete question.

Related

How can I dynamically change list of fields displayed in admin interface depending on the choice in the first field?

I want the admin interface to show disctrict field only if I choose 'B' as the category. If I choose 'W' I want all fields of Offer model to be displayed. Is it possible to show selected (filtered) fields in admin page depending on the choice in other field in the same model? Thanks in advance for your help.
My models:
class Category(models.Model):
NAME_CHOICES = (
('B', 'BLACK'),
('W', 'WHITE'),
)
name = models.CharField(max_length=200, choices=NAME_CHOICES)
class Meta:
verbose_name_plural = 'Categories'
def __unicode__(self):
return self.get_name_display()
class Offer(models.Model):
category = models.ForeignKey(Category, verbose_name='Kategoria')
city = models.CharField(max_length=128, verbose_name='Miasto')
province = models.CharField(max_length=3)
district = models.CharField(max_length=128, verbose_name='Dzielnica')
def __unicode__(self):
return "Offer number %s" % (self.id)
First of all I must to tell, that django works only in sync way. So if you want to choose which input to use, you must send a request and wait a feedback. In my opinion there're no straight way to do this task correctly.
And I see a few solutions:
1) You can use jQuery for that. But the main problem is that django has a own admin system with a built-in widgets. You can try to customize it in two ways:
Take an app with this option (for example, django-admin-tools) and create custom behavior on your form;
manage.py collectstatic and after that going to admin folder and create custom jQuery script.
2) Build a custom admin form for your model with ModelChoiceField. I don't quit sure about this field behavior really help you, but you can investigate that.
If I need to do this task, I choose first way with admin static and custom jQuery.

Django Admin Select Objects based on ManyToMany Relationship

I'm working on building a django app that extends the Mezzanine project. Mezzanine has a Gallery app (photos). I'd like to create "portfolio" page that acts as a landing page which has a single image and link to each gallery page.
Each gallery (Gallery) can have multiple images (GalleryImage). I'd like to via the admin select a gallery, then select an image to be displayed. However, I can't seem to figure out what to do.
Here's my model:
class GalleriesThumb(models.Model):
relatedlandingpage = models.ForeignKey(LandingPage)
relatedgallery = models.ForeignKey(Galleries)
thumb = models.ManyToManyField(GalleryImage)
def __unicode__(self):
return self.name
class Galleries(models.Model):
landingpage = models.ForeignKey(LandingPage)
tagline = models.CharField(max_length=100)
galleries = models.ManyToManyField(Gallery)
def __unicode__(self):
return self.name
class LandingPage(models.Model):
gallerytitle = models.CharField(max_length=200)
def __unicode__(self):
return self.name
My admin is something like:
class GalleryInline(admin.InlineModelAdmin)
model = Galleries
model = GalleriesThumb
list_display = galleries
list_display = thumb
class LangingPageAdmin(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('gallerytitle')
})
inlines = [GalleryInline,]
I realized that this won't do what i want, but how do I get the list_display on the the images that are related to Galleries. I'm pretty sure it needs to be a method, or am I taking a completing wrong approach if the selections that are made will be defining the content on the page. (I realize that I'm also missing my fields to store the selection in.)
I'm sorry if this a dumb question, but this my first real world attempt an app.
I think this link will resolve your problem
Django 1.2.1 Inline Admin for Many To Many Fields
class GalleryInline(admin.InlineModelAdmin)
model = Galleries.galleries.through

Django Country/City dynamic selection : how to allow field value out of queryset in Modelfield?

I am experiencing a problem with Django, trying to build a very simple (shame on me...) thing : I want to filter a ForeignKey ModelField, using another SelectField (with Ajax, for example : A "Country" selectbox autocompletes a "City" selectbox so the user can select his city).
My models look like :
class Country(models.Model):
...
class Town(models.Model):
country = models.ForeignKey(Country)
...
class Person(models.Model):
user = models.ForeignKey(User, unique=True)
town = models.ForeignKey(Town, related_name='persons')
...
Right.
Now I want a form to create/update a person, allowing the user to set a city in a selectbox filtered by the value of another selectbox (Country).
Here is the form I created :
class LazyChoiceField(forms.ChoiceField):
def __init__(self, required=True, widget=None, label=None,
initial=None, help_text=None, *args, **kwargs):
super(LazyChoiceField, self).__init__(required=required, widget=widget,
label=label, initial=initial,
help_text=help_text, *args, **kwargs)
def valid_value(self, value):
return True
class PersonForm(ModelForm):
country = ModelChoiceField(queryset=Country.objects.all())
town = LazyChoiceField(choices=[('0', "Chose a country firts!")])
class Meta:
model = Person
exclude = ('user', 'town', )
widgets = {
'country': forms.Select(attrs={'onchange':'get_towns();'}),
}
It works (providing a ajax.py file, serving a get_towns() function called via Ajax by Jquery script + a bit of JS stuff on the template page). But I am not really satisfied with this way of doing: excluding town field and adding another town LazyChoiceField doesn't seem DRY, does it?
My question is: is it possible to tell Django to not generate a validation error on a ModelField when the value selected is not in the initial queryset (actually, I can't render thousands of towns on the form !).
This feature (selecting Country/town) is very popular on the Web, but I didn't manage to find a simple Django snippet to do such a thing. What do you think about the solution I'm trying to develop? How to do this better and following the DRY principle?
If I don't exclude town from the ModelForm, my 30000 town selectbox makes the page really slow (and unusable)!
Thank you very much in advance!
Glad you solved your issue. Just in case anyone else happens by, Django builds validation for selects against the choices passed in during instantiation. If you're going to do AJAX filtering of selects, your choices as far as Django is concerned must be every allowable choice, unfiltered. Use javascript to make the choices whatever you like after the page is rendered.

Django admin TabularInline - is there a good way of adding a custom html column?

I've got a model (Entry) which contains a simple property:
#property
def image(self):
return str(self.id)+"_"+self.round.season.name+"_"+self.round.theme+"_"+self.person.name
I use this to build the name of a particular image file on disk. So I know that there's going to be an image at /path/to/images/(model.image()).jpg
I can display the raw image property itself within the TabularInline layout on an admin page by adding it to the readonly_fields collection, but how would I go about getting a column which had custom html wrapped around the model property?
e.g.
<img src="/images/{{model.image}}.jpg" />
What you can do is create a method in your TabularInline subclass that returns the HTML you want, then use that method's name in place of image in ImageInline.fields:
from django.utils.safestring import mark_safe
class ImageInline(admin.TabularInline):
...
fields = (..., 'render_image')
readonly_fields = (..., 'render_image')
def render_image(self, obj):
return mark_safe("""<img src="/images/%s.jpg" />""" % obj.image)
According to current Django 1.2+ I got errors "Form does not have such field as render_image". Solution is simple put the render_image function into model.Admin not in your inline form, second thing is fields and readonly_fields settings in your Inline form... So here You have what I've ended up with:
class OfferPropertyInline(admin.TabularInline):
model = OfferProperty
fields=('property_value',)
readonly_fields = ('property_value',)
class OfferAdmin(admin.ModelAdmin):
inlines = [
OfferPropertyInline
]
def property_value(self,obj):
return obj.get_value()
admin.site.register(Offer, OfferAdmin)
Lechup's answer does not work for me, I am using Django 1.11.7. I found this way to work around.
Let say I have 2 tables: Campaign and Article, one campaign has many articles. I want to show the articles when browsing a specific campaign.
Table Article has a column named score, which is a float. I want to round it up to 2 decimal places when viewing in Django admin.
This example shows how you can make a custom column for TabularInline in Django admin.
class Article(models.Model):
title = models.TextField(null=False)
url = models.TextField()
score = models.FloatField(null=True)
def __str__(self):
return self.title
def display_score(self):
if self.score:
return round(self.score, 2)
return self.score
display_score.short_description = 'Score'
class ArticleInline(admin.TabularInline):
model = Article
readonly_fields = ('title', 'url', 'display_score')
fields = ('title', 'url', 'display_score')
class CampaignAdmin(admin.ModelAdmin):
inlines = [ArticleInline]
admin.site.register(Campaign, CampaignAdmin)
#lechup correct except you need:
readonly_fields = ('mycustomfield',)
defined in the Inline for later versions of django (+1.4)

Django: InlineModelAdmin to reference its own Model

So I am trying to setup an entry posting system, where the user can select a bunch of related entries when creating an entry. And it would be wonderful if I could use the InlineModelAdmin for it. But it keeps wanting a foreignkey, which for some reason I'm unable to set up properly.
Here's a simplified setup of my situation:
models.py
class Entry(models.Model):
entry = models.ForeignKey('self', related_name='related_entry', null=True, blank=True)
title = models.CharField(max_length=100, verbose_name='title')
description = models.TextField(verbose_name='description')
def __unicode__(self):
return self.title
admin.py
class EntryInline(admin.TabularInline):
model = Entry
verbose_name = "related entry"
class EntryAdmin(admin.ModelAdmin):
inlines = [
EntryInline,
]
admin.site.register(Entry, EntryAdmin)
The problems im getting are of the likes:
DatabaseError at /admin/app/entry/add/
column app_entry.entry_id does not
exist LINE 1: SELECT "app_entry"."id",
"app_entry"."entry_id", "...
I'm still just kneedeep into the magic world of django, so if someone could point me out where I am going wrong that would be greatly appreciated!
First, I tried the code you provided in my machine (Django 1.2.3, Python 2.6.2, Ubuntu Jaunty) and it worked well as far as I could tell.
where the user can select a bunch of related entries when creating an entry.
Shouldn't you be using a ManyToMany relationship if you want an entry to be related to a bunch of entries? Your code currently defines a ForeignKey instead.
admin.py
...
admin.site.register(Entry, EntryAdmin)
Your admin is presently set up to let the user add an entry and also (optionally) one or more related entries in the same page (this worked perfectly). Was this your expectation?