customize select in django admin - python

I have a model, one field of it is a ForeignKey, so i see select in django admin, is it possiable to customize labels of this select?
class Model(models.Model):
name = models.CharField()
def __unicode__(self):
return self.name
class Part(models.Model):
name = models.CharField()
parent = model.ForeignKey(Model)
def __unicode__(self):
return self.name
def name_with_model(self):
return self.name + ' ' + parent.name
class SmallPart(models.Model):
name = models.CharField()
parent = model.ForeignKey(Part)
when I add new SmallPart I see select tag with names of parts, I need to see name_with_model

If you mean the field label:
using code from:
Django Admin - Overriding the widget of a custom form field
# forms.py
from django import forms
from django.contrib import admin
class ProductAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
self.fields['tags'].label = 'Custom Label'
Then, in your ModelAdmin object, you specify the form:
from django.contrib import admin
from models import Product
from forms import ProductAdminForm
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
admin.site.register(Product, ProductAdmin)
If you mean the labels in the select drop down:
Override the widget like in the answer above.
edit:
The default form field for a fk field is a model choice field. From the docs
The unicode method of the model
will be called to generate string
representations of the objects for use
in the field's choices; to provide
customized representations, subclass
ModelChoiceField and override
label_from_instance. This method will
receive a model object, and should
return a string suitable for
representing it. For example:
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.name_with_model()
and then:
class SmallPartAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SmallPartAdminForm, self).__init__(*args, **kwargs)
self.fields['parent'] = MyModelChoiceField(queryset=Part.objects.all())

Related

Accessing inline fields within inline get_formset in Django Admin

I have the following example where I have buildings(address, location,...) and apartments(name, size, type, building). One building containing multiple apartments.
class BuildingAdmin(admin.ModelAdmin):
inlines = [ApartmentInline,]
class ApartmentInline(admin.StackedInline):
def get_formset(self, request, obj=None, **kwargs):
formset = super(ApartmentInline, self).get_formset(request, obj=None, **kwargs)
#Here i'd like to see the values of inline fields, for example size or building that.
#Similar to how one can access ModelAdmin fields with obj.location within get_form
formset.form.base_fields["type"].widget = SelectMultiple(choices=custom_choices)
return formset
I'd like to be able to get the current apartments instance and field values when editing the object (for example size), so that I can create custom choices (querying other DB's or API's) for another field (type).
To modify the inline form widget you can override ModelInlineForm which gives each inline instance access after initializing.
from django.contrib import admin
from django.urls import resolve
class ApartmentInlineForm(forms.ModelForm):
class Meta:
model = A
fields = (
'...',
'...',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# calculate widget choices
self.fields['type'].widget = SelectMultiple(choices=custom_choices)
class ApartmentInline(admin.StackedInline):
model = Apartment
form = ApartmentInlineForm

Django: OneToOne dropdown in the admin interface and unique associations

Referring to Django - one-to-one modelAdmin i am still searching for a solution to my problem with the admin interface of Django and my OneToOne relationship.
I have the following model which extends the standard User model with an additional attribute is_thing_staff:
class ThingStaff(models.Model):
""" Extends the django user model by a separate model relationship which holds additional user
attributes
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
# by default a new user is not a staff member which take care of the thing administration
is_thing_staff = models.BooleanField(default=False)
def __str__(self):
return u"{}".format(self.user.username)
class Meta:
verbose_name = "Thing Staff"
verbose_name_plural = "Thing Staff"
If i create a new ThingStaff object in the django admin interface, i can select all users, even if there is already a relationship for a user. Saving a new object with a duplicate association to a user results in an error, that there is already an ThingStaff object associated with that User. So far this is more or less ok.
But why show up possible selections if they would result in an error in the next step? So i excluded them via
from django import forms
from django.contrib import admin
from .models import ThingStaff
class ThingStaffForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ThingStaffForm, self).__init__(*args, **kwargs)
self.fields['user'].queryset = User.objects.exclude(
id__in=ThingStaff.objects.values_list('user_id', flat=True)
)
#admin.register(ThingStaff)
class ThingStaffAdmin(admin.ModelAdmin):
form = ThingStaffForm
Great so far: The already associated users will not show up in the dropdown during the creation of a new ThingStaff object.
But if i want to change an existing association, the related user will also not show up in the dropdown which makes it impossible to reset the is_thing_staff flag.
So my question is: How can i enable this specific user again for the change view in the django admin interface?
Django's ModelForm distinguishes between add and change views (each one has it's on own method). This means that you can override it:
class ThingStaffAdmin(ModelAdmin):
def add_view(self, *args, **kwargs):
self.form = ThingStaffAddForm
return super().add_view(*args, **kwargs)
def change_view(self, *args, **kwargs):
self.form = ThingStaffChangeForm
return super().change_view(*args, **kwargs)
More in the docs:
https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.change_view
your exclution list must be updated and selected user for this ThingStaff must not excluded
update your form like this
class ThingStaffForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ThingStaffForm, self).__init__(*args, **kwargs)
exclude_user = ThingStaff.objects.all()
if self.instance:
exclude_user = exclude_user.exclude(pk=self.instance.pk)
self.fields['user'].queryset = User.objects.exclude(id__in=exclude_user.values('user_id'))
this code check if current form is edit form and have an instance exclude that from exclude list.

Django admin: Edit fields of one-to-one model class

I have two models with the following relationship defined in models.py:
class InnerModel(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class OuterModel(models.Model):
inner = models.OneToOneField(InnerModel)
def __str__(self):
return "OuterModel"
My forms.py looks like this:
class OuterModelForm(forms.ModelForm)
class Meta:
model = OuterModel
fields = ['inner']
My admin.py form looks like this:
class OuterModelAdmin(admin.ModelAdmin)
form = OuterModelForm
admin.site.register(OuterModel, OuterModelAdmin)
When I display the admin page, I can see the InnerModel instance and the name field is present, but the name field is an empty drop-down menu rather than a blank text field that can be edited.
How can I change the InnerModel name field so that it can be edited by admin?
You need to use inlines (doc):
class InnerModelInline(admin.StackedInline):
model = InnerModel
class OuterModelAdmin(admin.ModelAdmin):
inlines = [InnerModelInline]
admin.site.register(OuterModel, OuterModelAdmin)
Similar question: here

How to choose the value and label from Django ModelChoiceField queryset

I was trying to create a django form and one of my field contain a ModelChoiceField
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
When I try the code above what it return as an html ouput is
<option value="1">Person object</option>
on my Person Model I have the fields "id, fname, lname, is_active" . Is it possible to specify that my dropdown option will use "id" as the value and "lname" as the label? The expected html
should be
<option value="1">My Last Name</option>
Thanks in advance!
You can just add a call to label_from_instance in the init of Form ie
by adding something like
class TestForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
self.fields['field_name'].label_from_instance = self.label_from_instance
#staticmethod
def label_from_instance(obj):
return "My Field name %s" % obj.name
From the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField
The __unicode__ (__str__ on Python 3) method of the model will be
called to generate string representations of the objects for use in
the field’s choices; to provide customized representations, subclass
ModelChoiceField and override label_from_instance. This method will
receive a model object, and should return a string suitable for
representing it. For example:
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "My Object #%i" % obj.id
So, you can do that, or override __str__ on your model class to return the last name.
In your Person model add:
def __unicode__(self):
return u'{0}'.format(self.lname)
If you are using Python 3, then define __str__ instead of __unicode__.
def __str__(self):
return u'{0}'.format(self.lname)
You can overwrite label_from_instance method of the ModelChoiceField instance to your custom method. You can do it inside the __init__ method of the form
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['person'].label_from_instance = lambda instance: instance.name
to chose/change the value you can use "to_field_name" options ,
and to change the label of option you can overwrite the "label_from_instance" function inside ModelChoiceField class,
and here is a simple example ,
forms.py:
from django import forms
from .models import Group
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return f"My Object {obj.group_name}"
class MyPureDjangoForm(forms.Form):
group = MyModelChoiceField(queryset=Group.objects.all(),
widget=forms.Select(attrs={
'class': 'form-control'
}),
to_field_name='id',
)
for more information's kindly visit the following URL :
https://docs.djangoproject.com/en/3.2/ref/forms/fields/#django.forms.ModelChoiceField
i hope this helpful .
Similar to Thomas's answer, I recommend setting the label_from_instance method reference when creating the field. However, as I almost always want the model to have the same value for select field drop downs, I just define a get_select_field_label() method on the Model itself. This is especially useful when I want different select labels and __str__() representations. Heres a minimal example:
from django.db import models
from django import forms
class Book(models.Model):
author = models.ForeignKey("Author", on_delete=models.CASCADE)
title = models.TextField()
def __str__(self):
return f"{self.title}, by {self.author.full_name}"
def get_select_field_label(self):
return f"{self.title}"
class BookForm(forms.Form):
title = forms.ModelChoiceField(queryset=Book.objects.all(), widget=forms.Select())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label_from_instance = Book.get_select_field_label

Dynamically update ModelForm's Meta class

I am hoping to dynamically update a ModelForm's inline Meta class from my view. Although this code seems to update the exclude list in the Meta class, the output from as_p(), as_ul(), etc does not reflect the updated Meta exclude.
I assume then that the html is generated when the ModelForm is created not when the as_*() is called. Is there a way to force the update of the HTML?
Is this even the best way to do it? I just assumed this should work.
Thoughts?
from django.forms import ModelForm
from testprogram.online_bookings.models import Passenger
class PassengerInfoForm(ModelForm):
def set_form_excludes(self, exclude_list):
self.Meta.exclude = excludes_list
class Meta:
model = Passenger
exclude = []
The Meta class is used to dynamically construct the form definition - so by the time you've created the ModelForm instance, the fields not in the exclude have already been added as the new object's attributes.
The normal way to do it would be to just have multiple class definitions for each possible exclude list. But if you want the form itself to be dynamic, you'll have to create a class definition on the fly. Something like:
def get_form(exclude_list):
class MyForm(ModelForm):
class Meta:
model = Passenger
exclude = exclude_list
return MyForm
form_class = get_form(('field1', 'field2'))
form = form_class()
UPDATE: I just revisited this post and thought I'd post a little more idiomatic way to handle a dynamic class:
def PassengerForm(exclude_list, *args, **kwargs):
class MyPassengerForm(ModelForm):
class Meta:
model = Passenger
exclude = exclude_list
def __init__(self):
super(MyPassengerForm, self).__init__(*args, **kwargs)
return MyPassengerForm()
form = PassengerForm(('field1', 'field2'))
Another way:
class PassengerInfoForm(ModelForm):
def __init__(self, *args, **kwargs):
exclude_list=kwargs.pop('exclude_list', '')
super(PassengerInfoForm, self).__init__(*args, **kwargs)
for field in exclude_list:
del self.fields[field]
class Meta:
model = Passenger
form = PassengerInfoForm(exclude_list=['field1', 'field2'])
Similar approach, somewhat different goal (generic ModelForm for arbitrary models):
from django.contrib.admin.widgets import AdminDateWidget
from django.forms import ModelForm
from django.db import models
def ModelFormFactory(some_model, *args, **kwargs):
"""
Create a ModelForm for some_model
"""
widdict = {}
# set some widgets for special fields
for field in some_model._meta.local_fields:
if type(field) is models.DateField:
widdict[field.name] = AdminDateWidget()
class MyModelForm(ModelForm): # I use my personal BaseModelForm as parent
class Meta:
model = some_model
widgets = widdict
return MyModelForm(*args, **kwargs)
Use modelform_factory (doc):
from django.forms.models import modelform_factory
from testprogram.online_bookings.models import Passenger
exclude = ('field1', 'field2')
CustomForm = modelform_factory(model=Passenger, exclude=exclude) # generates ModelForm dynamically
custom_form = CustomForm(data=request.POST, ...) # form instance

Categories

Resources