Make boolean values editable in list_display? - django

I'd like a boolean field to be editable in my Django admin's list display. Instead, I have uneditable icons:
My code looks like this:
# Model
class Task(models.Model):
...
is_finished = models.BooleanField()
# Admin
list_display = (..., 'is_finished')
I haven't included is_finished in the readonly_fields tuple in admin.py, so I'm surprised that it isn't editable by default. What am I doing wrong?

ModelAdmin.list_editable is what you need, see its doc here. Below you also have an example:
class TaskAdmin(models.ModelAdmin):
list_display = (..., 'is_finished')
list_editable = ('is_finished',) # this MUST only contain fields that also are in "list_display"
#list_display_links = ('foo', 'bar') # this MUST NOT contain a field in common with "list_editable"

Related

Django Admin list_display: Link to filtered model

I want to make a music library app and in the admin/artist page I would like to link to a list with all the albums by the artist.
Models look like this:
class Artist(models.Model):
artistName = models.CharField(max_length=100, blank = False)
genre = models.CharField(max_length=100, blank = False)
def __str__(self):
return self.artistName
class Album(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
albumName = models.CharField(max_length=500, blank = False)
year = models.IntegerField(blank = False)
def __str__(self):
return self.albumName
Now for the admin part, I imagine that I make an ArtistAdmin model and then somehow use list_display to link to a filtered version of the Album model. Can anyone tell me how to do this or suggest a better way of doing it?
I'm having exactly the same need; here's the simplest way I came up so far.
Admin lists have list_filter, with which you can, for example, do that (assuming you register the admin classes and so on):
class AlbumAdmin(admin.ModelAdmin):
list_display = ('artist', 'albumName', 'year',)
list_filter = ('artist',)
This will add a right column in your admin site with the list of artists, and if you click on one, the Albums list will be filtered to show only those which are made by this artist.
Having that, and here's the trick, turns out that even if you don't declare "list_filter", Django will still filter things according to the URL parameters.
So, I activated the filters, I clicked in one of my "artists", so the list got filtered and I could see how it formats the URL, which is something like
[…]/admin/app_name/model_name/?foreignKeyName__id__exact=XX
In your case I imagine it will look similar to:
[…]/admin/music_library/album/?artist__id__exact=XX
Try to follow these steps, then remove the line "list_filter", and you will see that the URL still works.
So now we know that the only thing we need is to pass the param artist__id__exact=XX, so we have to append a column with a button for that.
class ArtistAdmin(admin.ModelAdmin):
list_display = ('artistName', 'genre', 'artists_list_field')
def artists_list_field(self, obj):
base_url = reverse('admin:music_library_album_changelist')
return mark_safe(u'Albums' % (
base_url, obj.id))
With this you have it working.
I hope it's useful!
You can use list_display and list_filter in admin.py like this:
class PersonAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name',)
list_filter = ('company__name', 'first_name',) # company is a model which has relation to the Person model
Then in your admin.py you should add PersonAdmin to your model :
admin.site.register(Person, PersonAdmin)
Reference

Django admin list_display reverse to parent

I have 2 models:
1: KW (individual keywords)
2: Project (many keywords can belong to many different projects)
class KW(models.Model):
...
project = models.ManyToManyField('KWproject', blank=True)
class KWproject(models.Model):
ProjectKW = models.CharField('Main Keyword', max_length=1000)
author = models.ForeignKey(User, editable=False)
Now when user is in Admin for KWproject they should be able to see all keywords belonging to selected project in list_display. I achieved this but it doesn't feel like proper way.
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
list_display = ('Keywordd', 'author')
def Keywordd(self, obj):
return '%s' % (obj.id, obj.ProjectKW)
Keywordd.allow_tags = True
Keywordd.admin_order_field = 'ProjectKW'
Keywordd.short_description = 'ProjectKW'
Is there better way to link and then list_display all items that have reverse relationship to the model? (via "project" field in my example)
As per the Django Admin docs:
ManyToManyField fields aren’t supported, because that would entail
executing a separate SQL statement for each row in the table. If you
want to do this nonetheless, give your model a custom method, and add
that method’s name to list_display. (See below for more on custom
methods in list_display.)
So, you may opt to implement a custom model method like so:
# models.py
class KW(models.Model):
...
project = models.ManyToManyField('KWproject', blank=True)
class KWproject(models.Model):
ProjectKW = models.CharField('Main Keyword', max_length=1000)
author = models.ForeignKey(User, editable=False)
def all_keywords(self):
# Retrieve your keywords
# KW_set here is the default related name. You can set that in your model definitions.
keywords = self.KW_set.values_list('desired_fieldname', flat=True)
# Do some transformation here
desired_output = ','.join(keywords)
# Return value (in example, csv of keywords)
return desired_output
And then, add that model method to your list_display tuple in your ModelAdmin.
# admin.py
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
list_display = ('Keywordd', 'author', 'all_keywords')
def Keywordd(self, obj):
return '%s' % (obj.id, obj.ProjectKW)
Keywordd.allow_tags = True
Keywordd.admin_order_field = 'ProjectKW'
Keywordd.short_description = 'ProjectKW'
Do take note: This can potentially be a VERY EXPENSIVE operation. If you are showing 200 rows in the list, then a request to the page will execute 200 additional SQL queries.

raw_id_fields and ManyToMany in Django admin

I want to use raw_id_fields on a ManyToMany relationship in the admin, and I want each related object to show up on its own row (as opposed to a comma-separated list in a single field, which is the default behavior). Following examples spotted in the wild, it seems like I should be able to do this:
# models.py
class Profile(models.Model):
...
follows = models.ManyToManyField(User,related_name='followees')
# admin.py
class FollowersInline(admin.TabularInline):
model = Profile
raw_id_fields = ('follows',)
extra = 1
class ProfileAdmin(admin.ModelAdmin):
search_fields = ('user__first_name','user__last_name','user__username',)
inlines = (FollowersInline,)
admin.site.register(Profile,ProfileAdmin)
But that generates the error:
<class 'bucket.models.Profile'> has no ForeignKey to <class 'bucket.models.Profile'>
I'm not clear what I'm doing wrong here. Thanks for suggestions.
Looks like you are setting the wrong model for your InlineAdmin
as the model for followers you are defining is User and not Profile.
Looking at the docs I'd say you should try:
class FollowersInline(admin.TabularInline):
model = Profile.follows.through
and
class ProfileAdmin(admin.ModelAdmin):
....
exclude = ('follows',)
inlines = (FollowersInline,)
In inline for m2m connection you should use through table and for raw_id_fields setting you should use fields from that through table - inside this table fields can be name different as you expect.
You need goes to sqlite3/psql etc. terminal to see through table schema and use propper field for raw_id_fields.
class FollowersInline(admin.TabularInline):
model = Profile.follows.through
# raw_id_fields = ("follows",) <- wrong
raw_id_fields = ("user",) # <- probably right, because your m2m relation with `User` table and django use name of that table to name field in `through` model

Django: Custom "add only" inline

I want an inline form to only show its fields contents, and not let users to edit or remove entries, only add them. That means that the values would be similar when using the readonly_fields option, and the "Add another ..." link at the bottom would make a form appear, letting users add more entries.
The can_delete option it's useful here, but the readonly_fields lock both add and change possibilities. I imagine that building a new inline template would do. In that case, how would I just show the field values for each entry and then put a form at the bottom?
Edit: what I got until now:
# models.py
class AbstractModel(models.Model):
user = models.ForeignKey(User, editable = False)
... some more fields ...
class Meta:
abstract = True
class ParentModel(AbstractModel):
... fields ...
class ChildModel(AbstractModel):
parent = models.ForeignKey(ParentModel, ... options ...)
... fields ...
# admin.py
class ChildModelInline(admin.TabularInline):
model = ChildModel
form = ChildModelForm
can_delete = False
class ParentModelAdmin(admin.ModelAdmin):
... options ...
inlines = (ChildModelInline,)
# forms.py
class ChildModelForm(models.ModelForm):
user = forms.CharField(required = False)
... some more fields and stuff needed ...
def __init__(self, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
try: user = User.objects.get(id = self.instance.user_id)
except: return None
self.fields['user'].initial = user.first_name
self.fields['user'].widget.attrs['readonly'] = 'readonly'
In this example I'm doing like I wanted the user field as readonly.
In the last line, If I change the widget attribute to ['disabled'] = True, it works fine, but I need a text entry, not a disabled form field. I'm also aware that I'll need to override the save_model() and save_formsets() for this to work properly.
I would use extra=1 to get that last working form.
Then loop through all the forms except the last one in your view and change, like this, every field: In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?
You don't have to do it in the __init__, you can access those attributes after the entire formset is created of course.

Django admin: How to display a field that is marked as editable=False' in the model?

Even though a field is marked as 'editable=False' in the model, I would like the admin page to display it. Currently it hides the field altogether.. How can this be achieved ?
Use Readonly Fields. Like so (for django >= 1.2):
class MyModelAdmin(admin.ModelAdmin):
readonly_fields=('first',)
Update
This solution is useful if you want to keep the field editable in Admin but non-editable everywhere else. If you want to keep the field non-editable throughout then #Till Backhaus' answer is the better option.
Original Answer
One way to do this would be to use a custom ModelForm in admin. This form can override the required field to make it editable. Thereby you retain editable=False everywhere else but Admin. For e.g. (tested with Django 1.2.3)
# models.py
class FooModel(models.Model):
first = models.CharField(max_length = 255, editable = False)
second = models.CharField(max_length = 255)
def __unicode__(self):
return "{0} {1}".format(self.first, self.second)
# admin.py
class CustomFooForm(forms.ModelForm):
first = forms.CharField()
class Meta:
model = FooModel
fields = ('second',)
class FooAdmin(admin.ModelAdmin):
form = CustomFooForm
admin.site.register(FooModel, FooAdmin)
Add the fields you want to display on your admin page.
Then add the fields you want to be read-only.
Your read-only fields must be in fields as well.
class MyModelAdmin(admin.ModelAdmin):
fields = ['title', 'author', 'published_date', 'updated_date', 'created_date']
readonly_fields = ('updated_date', 'created_date')
You could also set the readonly fields as editable=False in the model (django doc reference for editable here). And then in the Admin overriding the get_readonly_fields method.
# models.py
class MyModel(models.Model):
first = models.CharField(max_length=255, editable=False)
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
return [f.name for f in obj._meta.fields if not f.editable]
With the above solution I was able to display hidden fields for several objects but got an exception when trying to add a new object.
So I enhanced it like follows:
class HiddenFieldsAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
try:
return [f.name for f in obj._meta.fields if not f.editable]
except:
# if a new object is to be created the try clause will fail due to missing _meta.fields
return ""
And in the corresponding admin.py file I just had to import the new class and add it whenever registering a new model class
from django.contrib import admin
from .models import Example, HiddenFieldsAdmin
admin.site.register(Example, HiddenFieldsAdmin)
Now I can use it on every class with non-editable fields and so far I saw no unwanted side effects.
You can try this
#admin.register(AgentLinks)
class AgentLinksAdmin(admin.ModelAdmin):
readonly_fields = ('link', )