Django Admin override displayed field value - django

I have following model:
class Model(models.Model):
creator = models.ForeignKey(User,related_name='com_creator',on_delete=models.SET_NULL, blank=True, null=True)
username = models.CharField(max_length=60,default="")
created = models.DateTimeField(auto_now_add=True)
body = models.TextField(max_length=10000,default=" ")
subtype = models.CharField(_("SubType"),max_length=100)
typ = models.CharField(_("Type"),max_length=50)
plus = models.ManyToManyField(User,related_name='com_plus', verbose_name=_('Plus'), blank=True)
is_anonymous = models.BooleanField(_('Stay Anonymous'), blank=True, default=False)
The values in typ and subtype are codes, like: "6_0_p", (because the original values are ridiculosly long, so I use codes and a dict to translate to human readable form).
QUESTION: How can I intercept these values in django admin and translate them to human readable form?
This is what i tried so far:
class ModelAdmin(admin.ModelAdmin):
model = Model
extra = 1
exclude = ("username","creator","plus" )
readonly_fields = ('subtype','typ','is_anonymous','created')
fields = ('body','subtype','typ')
def typ(self, obj):
self.typ = "ppp"
obj.typ = "ppp"
return "pppp"
I tried returning the object, self, some other value. I also tried to set the value without using a callable just declaring "typ='xxx'". Nothing. I probably don't understand how this whole thing works ...
Any ideas will be appreciated.

You need to create a readonly_field which corresponds to your method name, then return anything from that method.
Documentation here:
https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields
class MyAdmin(admin.ModelAdmin):
readonly_fields = ('my_custom_field',)
def my_custom_field(self, obj):
return 'Return Anything Here'

I think that your code doesn't work because the method name is the same as the field.
You have to change the name of field, like _typ in fields tuple, then add a method called _typ that return anything or some obj attr.
i.e:
class ModelAdmin(admin.ModelAdmin):
model = Model
extra = 1
exclude = ("username","creator","plus" )
readonly_fields = ('subtype','typ','is_anonymous','created')
fields = ('body','subtype','_typ')
def _typ(self, obj):
return "pppp"

In Django 4 and above you can add a custom (readonly) field easily, like this:
class FooAdmin(admin.ModelAdminAbstractAdmin):
readonly_fields = [
'_dynamic_field',
]
fieldsets = [
('Form', {
'fields': (
'_dynamic_field',
),
'classes': ('form-group',)
}),
]
def _dynamic_field(self, obj=None):
if obj is not None:
return 'Bar'
return "Foo"
# to change the label
_dynamic_field.__name__ = "Dynamic field label"
How you can see, the field needs to be in readonly_fields and fieldsets too.

Related

How can I show model information inside another model? in django

So, I have three models , and there are connected with foreign key.
I'm using list_display on "avaria" , and I want to show the name of the "pavimento" inside of the model "avaria". What is the best options to that?
class pavimento(models.Model):
pavimento_nome = models.CharField("Pavimento",max_length=200)
class avaria(models.Model):
avaria_consumidores = models.IntegerField(blank=True,null=True,verbose_name="Consumidores")
class pavimentacao(models.Model):
pavimentacao_id=models.ForeignKey(avaria,related_name='avariaObjects',on_delete=models.CASCADE)
pavimentacao_avaria = models.ForeignKey(pavimento,on_delete=models.CASCADE, verbose_name="Pavimento")
You can write a property for avaria
#property
def pavimento_names(self):
pavimentacaos = self.avariaObjects.select_related('pavimentacao_avaria').values_list('pavimentacao_avaria__pavimento_nome', flat=True)
return ', '.join(pavimentacaos)
And add 'pavimento_names' both to readonly_fields and list_display lists of avaria admin.
Or you can directly add method into your admin like:
class AvariaAdmin(admin.ModelAdmin):
list_display = (..., 'get_pavimento_names')
def get_pavimento_names(self, obj):
pavimentacaos = obj.avariaObjects.select_related('pavimentacao_avaria').values_list('pavimentacao_avaria__pavimento_nome', flat=True)
return ', '.join(pavimentacaos)
get_pavimento_names.short_description = 'Pavimento Names'
get_pavimento_names.admin_order_field = 'avariaObjects__pavimentacao_avaria__pavimento_nome'

Django Rest Framework update nested serializer

I have a big misunderstanding with DRF nested serializers. I read docs about this and found out that I need to provide my own update method. So, here it is:
class SkillsSerializer(serializers.ModelSerializer):
class Meta:
model = Skills
class ProfileSerializer(serializers.ModelSerializer):
skills = SkillsSerializer(many=True)
class Meta:
model = Profile
fields = ('user', 'f_name', 'l_name', 'bd_day', 'bd_month', 'bd_year', 'spec', 'company', 'rate', 'skills', 'bill_rate', 'website', 'about', 'city', 'avatar', 'filled')
def update(self, instance, validated_data):
instance.user_id = validated_data.get('user', instance.user_id)
instance.f_name = validated_data.get('f_name', instance.f_name)
instance.l_name = validated_data.get('l_name', instance.l_name)
instance.bd_day = validated_data.get('bd_day', instance.bd_day)
instance.bd_month = validated_data.get('bd_month', instance.bd_month)
instance.bd_year = validated_data.get('bd_year', instance.bd_year)
instance.spec = validated_data.get('spec', instance.spec)
instance.company = validated_data.get('company', instance.company)
instance.rate = validated_data.get('rate', instance.rate)
instance.website = validated_data.get('website', instance.website)
instance.avatar = validated_data.get('avatar', instance.avatar)
instance.about = validated_data.get('about', instance.about)
instance.city = validated_data.get('city', instance.city)
instance.filled = validated_data.get('filled', instance.filled)
instance.skills = validated_data.get('skills', instance.skills)
instance.save()
return instance
I compared it with docs and didn't found any difference. But in this case, when I try to update skills, it doesn't work. And there is a real magic: when I put this
instance.skills = validated_data.get('bd_day', instance.skills)
It works PERFECTLY WELL! For ex., if I put bd_day = 12, update method saves instance with skills with ID's 1 and 2.
So, it seems like serializer ignores skills from AJAX data and still thinking, that skills serializer is read_only.
So, what is a point of this logic and how I can finally update my skills?
UPDATE
My models:
class Skills(models.Model):
tags = models.CharField(max_length='255', blank=True, null=True)
def __unicode__(self):
return self.tags
class Profile(models.Model):
user = models.OneToOneField(User, primary_key=True)
...
skills = models.ManyToManyField(Skills, related_name='skills')
...
UPDATE2
Still doesn't have any solution for this case! I tried this and this - the same result.
It seems that serializer ignored JSON data at all.
I had to update the answer, you did it inefficient way, so see the solution, it's far better
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('user', 'f_name', ... 'skills', ... 'filled')
depth = 1
class ProfileUpdateSerializer(serializers.ModelSerializer):
skills = serializers.PrimaryKeyRelatedField(many=True, queryset=Skills.objects.all(), required=False)
class Meta:
model = Profile
fields = ('user', 'f_name', ... 'skills', ... 'filled')
def update(self, instance, validated_data):
user = validated_data.pop('user', {})
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if user:
User.objects.filter(id=self.context['request'].user.id).update(**user)
return instance
But after that I had another issue. I can received only one element from array of skills. And I found solution here:
$.ajax({
url: myurl,
type: 'PUT',
dataType: 'json',
traditional: true,<-----THIS!
data: data,
And that's it! It works like a charm!
I hope, my solution will be useful!
You have an issue here as you're providing non model data.
this:
instance.skills = validated_data.get('skills', instance.skills)
Will not provide Skill model instances but a dictionary.
You need to get the skills instance first and then inject them back to the instance.skills.

Django - clean_field() is not being called for updates on admin inline form

I can not figure out why a clean_field() method is not being called for an inline form that is updated on an admin view. The code I have seems straight-forward (see synopsis below).
When I modify the Primary form through admin interface (http://admin/..../primary/1/), as expected, I see:
Admin.PrimaryAdminForm.clean_myfield() called
Admin.PrimaryAdminForm.clean() called
Model.Primary.clean() called
However, when I modify the Primary as seen as an inline on the Admin view of Membership (http://admin/..../membership/1/), I only see:
Model.Primary.clean() called
I have tried placing the "def clean_myfield(self):" method in the following locations but can not see it get executed from the Membership inlined Primary form:
Model.Primary.clean_myfield
Admin.PrimaryAdmin.clean_myfield
Admin.PrimaryAdminForm.clean_myfield
Admin.PrimaryAdminInline.clean_myfield
Is there somewhere else this clean_myfield code should be placed?
I have read (and reread) the Django docs on [forms and field validation][docs.djangoproject.com/en/dev/ref/forms/validation/#form-and-field-validation] which gives a great coverage, but there's nothing on inline validation. I've also read docs.djangoproject.com/en/dev/ref/contrib/admin/#adding-custom-validation-to-the-admin, but no help for inline specific validation. Is there other documentation on this?
---> Answered by Austin provided a doc reference to: "If not specified" (see his link) , which implies the answer. I added a request to improve the documents on this topic.
After further experimenting I found a workaround by putting code in the Model.Primary.clean() method:
def clean(self):
data = self.myfield
data += "_extra" # not actual cleaning code
self.myfield = data
So the question remains: Why is Model.clean() seem to be the only place to put admin inline form validation and not in a clean_myfield(self) method?
---> Answered by Austin. I needed add form = PrimaryAdminForm to PrimaryInline. With this addition, PrimaryAdminForm.clean_myfield(self) is called when PrimaryInline myfield is updated on Membership form. Code ordering was updated due to the added form reference.
Code synopsis:
No forms.py file -- all models are updated through admin interface
models.py:
class Membership(models.Model):
name = models.CharField( max_length=NAME_LENGTH,
null=True, blank=True, verbose_name="Membership Name Tag",
help_text="Name of membership" )
class Primary(models.Model):
user = models.OneToOneField(User, verbose_name="User Name")
membership = models.OneToOneField(Membership, verbose_name="Membership Name")
myfield = models.CharField("My Field", max_length=20, null=True, blank=True)
# clean method altered as in Update comment
# Why must this be here? Why not in clean_myfield(self)
def clean(self):
data = self.myfield
data += "_extra" # not actual cleaning code
self.myfield = data
admin.py:
class MembershipAdminForm(ModelForm):
class Meta:
model = Membership
class PrimaryAdminForm(ModelForm):
class Meta:
model = Primary
def clean_myfield(self):
data = self.cleaned_data['myfield']
data += "_extra" # not actual cleaning code
return unicode(data)
def clean(self):
cleaned_data = super(PrimaryAdminForm, self).clean()
# not actual cleaning code
return cleaned_data
# EDIT2: Moved PrimaryInline so it's defined after PrimaryAdminForm
class PrimaryInline(admin.StackedInline):
model = Primary
form = PrimaryAdminForm #EDIT2 as recommended by Austin
raw_id_fields = ['user']
verbose_name_plural = 'Primary Member'
fieldsets = ((None, {'classes': ('mbship', ),
'fields': ('user', 'myfield')}), )
class MembershipAdmin(admin.ModelAdmin):
form = MembershipAdminForm
# inlines
inlines = [PrimaryInline, ]
fieldsets = ((None, {'classes': ('mbship',),
'fields': ('name'), }), )
class PrimaryAdmin(admin.ModelAdmin):
form = PrimaryAdminForm
list_display = ('__unicode__', 'user', 'status', 'date_used' )
search_fields = ['user__first_name', 'user__last_name', 'user__email']
fieldsets = ((None, {'classes': ('mbship',),
'fields': ('user', 'membership', 'myfield'), }), )
def clean_myfield(self):
data = self.cleaned_data['myfield']
data += "_extra" # not actual cleaning code
return unicode(data)
Validation occurs on ModelForm objects, not ModelAdmin objects. If you want to override any clean methods then you have to create your own ModelForm descendent for each required model.
In your example, the PrimaryInline class does not specify a form. If not specified, the form used will be a ModelForm, which doesn't have any of your custom clean methods.
Try this:
class PrimaryInline(admin.StackedInline):
# ... existing code ...
form = PrimaryAdminForm
This will now use your custom PrimaryAdminForm with associated clean() methods.

Can't display DateField on form with auto_now = True

I have a model with auto_now, and auto_now_add set for Update and Create fields:
class HotelProfiles(models.Model):
fe_result_id = models.AutoField(primary_key=True)
fe_created_date = models.DateTimeField(verbose_name='Created',
blank=True,
auto_now_add=True)
fe_updated_date = models.DateTimeField(verbose_name='Updated',
blank=True,
auto_now=True)
In the Admin it displays both fields but leaves them uneditable. They
don't seem to be passed to my form to be rendered. I don't want them
to be editable, but I would like to display at the top of my form.
How can I do this?
This is in my HotelProfilesAdmin class:
readonly_fields = ('fe_result_id', 'fe_created_date', 'fe_updated_date', 'fe_owner_uid')
#date_hierarchy = 'lto_end_date'
fieldsets = (
("Internal Use Only", {
'classes': ('collapse',),
'fields': ('fe_result_id', 'fe_created_date', 'fe_owner_uid', 'fe_updated_date', 'fe_result_status')
}),
Make the fields you want readonly
explicitly override what fields are available in this admin form (readonly fields will be present but readonly)
Example:
from django.contrib import admin
class HotelProfilesAdmin(admin.ModelAdmin) :
# Keep the fields readonly
readonly_fields = ['fe_created_date','fe_updated_date']
# The fields in the order you want them
fieldsets = (
(None, {
'fields': ('fe_created_date', 'fe_updated_date', ...other fields)
}),
)
# Add your new adminform to the site
admin.site.register(HotelProfiles, HotelProfilesAdmin)
For the benefit of others, I figured out a way to do this. I'm new to Django, so if there is a better way, I'd be interested in hearing it. The view code is below. I wasn't sure if Django was not returning the fields from the query, and I found out that it was. So, something in the renderering of the form that I don't understand removed those fields so they couldn't be rendered. So, I copied them to a dict called read_only before rendering and passed it along.
try:
hotel_profile = HotelProfiles.objects.get(pk=hotel_id)
read_only["created_on"] = hotel_profile.fe_created_date
read_only["updated_on"] = hotel_profile.fe_updated_date
f = HotelProfileForm(instance=hotel_profile)
#f.save()
except:
f = HotelProfileForm()
print 'rendering blank form'
return render_to_response('hotels/hotelprofile_form.html', {'f' : f, 'read_only': read_only}, context_instance=RequestContext(request))

field choices() as queryset?

I need to make a form, which have 1 select and 1 text input. Select must be taken from database.
model looks like this:
class Province(models.Model):
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=30)
def __unicode__(self):
return self.name
It's rows to this are added only by admin, but all users can see it in forms.
I want to make a ModelForm from that. I made something like this:
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=CHOICES),
}
but it doesn't work. The select tag is not displayed in html. What did I wrong?
UPDATE:
This solution works as I wanto it to work:
class ProvinceForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProvinceForm, self).__init__(*args, **kwargs)
user_provinces = UserProvince.objects.select_related().filter(user__exact=self.instance.id).values_list('province')
self.fields['name'].queryset = Province.objects.exclude(id__in=user_provinces).only('id', 'name')
name = forms.ModelChoiceField(queryset=None, empty_label=None)
class Meta:
model = Province
fields = ('name',)
Read Maersu's answer for the method that just "works".
If you want to customize, know that choices takes a list of tuples, ie (('val','display_val'), (...), ...)
Choices doc:
An iterable (e.g., a list or tuple) of
2-tuples to use as choices for this
field.
from django.forms.widgets import Select
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=( (x.id, x.name) for x in CHOICES )),
}
ModelForm covers all your needs (Also check the Conversion List)
Model:
class UserProvince(models.Model):
user = models.ForeignKey(User)
province = models.ForeignKey(Province)
Form:
class ProvinceForm(ModelForm):
class Meta:
model = UserProvince
fields = ('province',)
View:
if request.POST:
form = ProvinceForm(request.POST)
if form.is_valid():
obj = form.save(commit=True)
obj.user = request.user
obj.save()
else:
form = ProvinceForm()
If you need to use a query for your choices then you'll need to overwrite the __init__ method of your form.
Your first guess would probably be to save it as a variable before your list of fields but you shouldn't do that since you want your queries to be updated every time the form is accessed. You see, once you run the server the choices are generated and won't change until your next server restart. This means your query will be executed only once and forever hold your peace.
# Don't do this
class MyForm(forms.Form):
# Making the query
MYQUERY = User.objects.values_list('id', 'last_name')
myfield = forms.ChoiceField(choices=(*MYQUERY,))
class Meta:
fields = ('myfield',)
The solution here is to make use of the __init__ method which is called on every form load. This way the result of your query will always be updated.
# Do this instead
class MyForm(forms.Form):
class Meta:
fields = ('myfield',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the query here
MYQUERY = User.objects.values_list('id', 'last_name')
self.fields['myfield'] = forms.ChoiceField(choices=(*MYQUERY,))
Querying your database can be heavy if you have a lot of users so in the future I suggest some caching might be useful.
the two solutions given by maersu and Yuji 'Tomita' Tomita perfectly works, but there are cases when one cannot use ModelForm (django3 link), ie the form needs sources from several models / is a subclass of a ModelForm class and one want to add an extra field with choices from another model, etc.
ChoiceField is to my point of view a more generic way to answer the need.
The example below provides two choice fields from two models and a blank choice for each :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=([['','-'*10]]+[[x.id, x.__str__()] for x in Speakers.objects.all()]))
event = forms.ChoiceField(choices=( [['','-'*10]]+[[x.id, x.__str__()] for x in Events.objects.all()]))
If one does not need a blank field, or one does not need to use a function for the choice label but the model fields or a property it can be a bit more elegant, as eugene suggested :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=((x.id, x.__str__()) for x in Speakers.objects.all()))
event = forms.ChoiceField(choices=(Events.objects.values_list('id', 'name')))
using values_list() and a blank field :
event = forms.ChoiceField(choices=([['','-------------']] + list(Events.objects.values_list('id', 'name'))))
as a subclass of a ModelForm, using the one of the robos85 question :
class MixedForm(ProvinceForm):
speaker = ...