Display a calculated field value in a ModelForm - django

I have an object Performance model in my Django project.
class Performance(models.Model):
performance_id = models.AutoField(primary_key=True)
name = models.CharField(_('Nom'), max_length=64)
code = models.CharField(_('Code'), max_length=16, db_index=True, default='')
type = models.InterField(...)
value = models.CharField(...)
In the Admin interface, I have a dedicated PerformanceInlineAdmin and a PerformanceInlineForm class.
In a Performance object, if the value field starts with "$" then the field contains kind of Reverse Polish Notation expression (such as "100 450 +"...).
In this case, the value displayed shall be the calculated expression instead of the field plaintext value.
But I still haven't found a reliable solution. Not even sure that it should be handled in the Form object.
Any suggestion is welcome.
Z.

You can define a method inside your InlineAdmin class that returns a read-only field:
class PerformanceInlineAdmin(admin.TabularInline):
model = Performance
fields = ['name', 'code', 'type', 'calculated_value']
readonly_fields = ['calculated_value', ]
def calculated_value(self, instance):
if instance.value.startswith("$"):
return polish_calc(instance.value.strip('$')
else:
return instance.value
calculated_value.short_description = "Calculated value"
Note that if you define a calculated_value() method on Performance itself, you can skip the whole method in the inline admin class and just declare it in readonly_fields.

Related

Django choice field populated by queryset - save id but show value

I have two models, Tag and TagGroup.
class TagGroup(models.Model):
tag_group_name = models.CharField(max_length=100)
class Tag(models.Model):
tag_name = models.CharField(max_length=100)
tag_group = models.ForeignKey(TagGroup, blank=True, null=True)
I've put a TagGroup form as a choice field into a template so that I can assign a TagGroup to a Tag. I created this form to populate from a TagGroup queryset.
class TagGroupForm(ModelForm):
tag_group_name = forms.ModelChoiceField(queryset=TagGroup.objects.values_list('id', 'tag_group_name'), required=False)
class Meta:
model = TagGroup
fields = [
'tag_group_name'
]
I haven't seen any obvious instructions how I can assign the Id to the Tag table while showing the user only the Tag value in the choice field in the template.
Currently the above shows:
Couple of questions:
is the queryset correct? I have tried without "values_list" but it then just shows an "Object" in the form field in template?
how do i 'hide' the Id so i can save on it, while only showing the user the actual string value in the form field?
Edited to add updated form:
class TagGroupForm(ModelForm):
tag_group_name = forms.ModelChoiceField(queryset=TagGroup.objects.all(), to_field_name = 'tag_group_name', required=False)
class Meta:
model = TagGroup
fields = [
'tag_group_name'
]
this now produces the following .. looks close .. the form value has a nice string but the actual value displayed to user is still "TagGroup object". How to get this to show?
From the docs,
The str (unicode on Python 2) method of the model will be called to generate string representations of the objects for use
So simply just assign this to the objects name and all will be ok! (Also, you don't need to use values_list) The reason it shows the Object by default is because this is what the default string representation is.
class TagGroup(models.Model):
tag_group_name = models.CharField(max_length=100)
def __str__(self):
return self.tag_group_name
tag_group_name = forms.ModelChoiceField(queryset=TagGroup.objects.all(), required=False)
Alternatively, if you don't wish to modify this and wish to reserve it for other uses.
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
class TagChoiceField(ModelChoiceField):
queryset = TagGroup.objects.all()
def label_from_instance(self, obj):
return obj.tag_group_name # or similar

Model Method from rest_framework modelSerializer

this is a simple question but I'm very new to django-rest-framework.
I was wondering if there is any way to access a method defined on the model from the serializer.?
Say I have a model
class Listing(models.Model):
listingid = models.BigIntegerField(primary_key=True)
mappingid = models.BigIntegerField()
projectlevelid = models.IntegerField()
subsellerid = models.IntegerField()
iscreatedbyadmin = models.BooleanField(default=None, null=True)
createdon = models.DateTimeField(auto_now_add=True, editable=False)
validationstatus = models.SmallIntegerField(default=0)
def is_project(self):
""" Returns True if listing is of Project Type (projectlevelid=6) else False"""
if self.projectlevelid == 6:
return True
else:
return False
def get_project_info(self):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if self.is_project() == False:
return False
return models.Project.objects.get(projectid=self.mappingid)
Is it possible for the serializer
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = models.MasterListing
to have access to Listing.is_project i.e. for an object of the Listing model, can the serializer call its is_project method?
If so, can I set a field in the serializer such that if is_project returns true, the field is populated?
I am trying for something like this,
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SomeRELATEDFieldTYPE() # this field if populated if the `is_project` is true
class Meta:
model = models.MasterListing
I understand I can do this using some combination of required=False and SerializerMethodField, but maybe there is a simpler way?.
Note: It is not possible for me to set a foreign key to the mappingid, since it depends on the projectlevelid. I also can't affect this relationship so no further normalization is possible. I know that there might be some way using content-types, but we are trying to avoid that if it is possible..
EDIT: I solved the problem, but not as the question specified.
I used this:
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SerializerMethodField()
def get_project(self, obj):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if str(obj.projectlevelid) == str(6):
projectObj = models.Project(projectid=obj.mappingid)
projectObjSerialized = ProjectSerializer(projectObj)
return projectObjSerialized.data
return False
class Meta:
model = models.MasterListing
So, the original question still stands: "Is it possible for the modelSerializer to access its models methods?"
Also, another problem that now appears is, can I make the serializer exclude fields on demand, i.e. can it exclude mappingid and projectlevelid if it is indeed a project?
For your first question source attribute is the answer, citing:
May be a method that only takes a self argument, such as
URLField('get_absolute_url')
For your second answer, yes it is also possible. Check the example it provides in their docs: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
PS: I really love drf for its very complete documentation =).
EDIT
To use the source attribute you can just declare a new explicit field like so:
is_project = serializers.BooleanField(source='is_project')
With this, is_project field has the value of the is_project method of your instance. Having this, when creating the dynamic serializer (by modifying its init method) you can add the 'project' field if it's True.
#argaen is absolutely right, source is a DRF core argument, and would most definitely solve your problem. However, it's redundant to use source, if the field name is the same as the source. So the above answer won't require you specify source, since field name is_project is the same as source name is_project.
So in your case:
is_project = serializers.BooleanField()

Is there a way to get type of related field from a model queryset in Django?

Can I get type of related field from a model queryset?
Let consider example model:
class Semester(models.Model):
active = models.BooleanField(default=False, verbose_name="Active")
class Subject(models.Model):
name = models.CharField(max_length=100, verbose_name="Name")
semester = models.ForeignKey(Semester, verbose_name="Semester")
if I have some field name in variable and queryset I can do this:
querySet = Subject.objects.all()
some_field_name = 'name'
field_type = querySet.model._meta.get_field(some_field_name).get_internal_type()
Is there any way to get related field type, for example:
querySet = Subject.objects.all()
some_field_name = 'semester__active'
field_type = ?
Try using get_field_by_name:
field_type = querySet.model._meta.get_field_by_name(some_field_name).get_internal_type()
From Django's source code:
def get_field_by_name(self, name):
"""
Returns the (field_object, model, direct, m2m), where field_object is
the Field instance for the given name, model is the model containing
this field (None for local fields), direct is True if the field exists
on this model, and m2m is True for many-to-many relations. When
'direct' is False, 'field_object' is the corresponding RelatedObject
for this field (since the field doesn't have an instance associated
with it).
Uses a cache internally, so after the first access, this is very fast.
"""
Also try:
field = querySet.model._meta.get_field_by_name("semester")
field_type = field[0].rel.to._meta.get_field_by_name("active").get_internal_type()
Thanks for any help!
I find solution with some help from this answer:
main, related = some_field_name.split("__")
field_type = querySet.model._meta.get_field(main).rel.to._meta.get_field(related).get_internal_type()

How to assign default value to Django model Integerfield, while related FormField is optional?

I want to create a Django model Field (IntegerField) with a default value, and also create a form derived from the model, where the field is optional. If it's not set on the form, then when I save the form, I want the default value saved to the DB.
# model.py
class Invoice(models.Model):
# IntegrityError "Column 'expireDays' cannot be null"
expireDays = models.PositiveSmallIntegerField(default=1)
# expireDays = *null* in DB
expireDays = models.PositiveSmallIntegerField(default=1, blank=True, null=True)
# forms.py
class InvoiceForm(forms.ModelForm):
# leaving this line out gives invalid form
expireDays = forms.IntegerField(required=False)
class Meta:
model = Invoice
(I used only one of the field declaration lines at a time. :)
I'm not even sure that I'm declaring the default value correctly. The only reference I could find to it was in an article on handling choices by James Bennett. I have yet to find it in the Django docs (I'm using version 1.2 - maybe it's in 1.3?)
Update - I tried setting the field's default value in the MySql database, to no effect. It seems as if, even when the form does not have a value for the field, it goes ahead and assigns null to the DB, over-riding the MySql default value.
Although I am currently just setting a default value in the view that creates the form - I don't really like that, since it puts the responsibility for the field's integrity in the view, not the DB.
The way I would have thought it would work, is that the field could be set, or not, in the form - if set, that value would be written to the DB, and if not set, the DB default would be used. Instead, if not set, the form is writing a null to the DB. So what's the point of having a default value in the ModelField declaration if it's not used? What exactly does it do?
i you want field to be optional - just leave second definition in the model and do not add anything in the form definition:
class Invoice(models.Model):
expireDays = models.PositiveSmallIntegerField(default=1, blank=True, null=True)
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoice
update, so in case there is no value set, use 1 as the field value:
class InvoiceForm(forms.ModelForm):
def clean_expireDays(self):
exp_days = self.cleaned_data.get('expireDays')
if exp_days is None:
return self.fields['expireDays'].initial
# above can be: return 1
# but now it takes value from model definition
else:
return exp_days
class Meta:
model = Invoice

Intermediate model formset validation

I am wondering how to specify some constraints on intermediate model formset.
I have 3 classes in model:
Attribute, Product and AttributeValuation, which is intermediate for Attribute and Product:
class Attribute(models.Model):
type = models.CharField(max_length = 200)
pass
class Product(models.Model):
attribute_values = models.ManyToManyField(Attribute, through='AttributeValuation')
class AttributeValuation(models.Model):
attribute = models.ForeignKey(Attribute)
product = models.ForeignKey(Product)
On top of that, I have built AttributeValuationInline with AttributeFormset, and registered it to ProductAdmin:
class AttributeValuationInline(admin.TabularInline):
model = AttributeValuation
extra = 0
formset = AttributeFormset
class ProductAdmin(admin.ModelAdmin):
inlines = (AttributeValuationInline,)
class AttributeFormset(BaseInlineFormSet):
def clean(self):
pass
My question is: how can I check in the clean method the contents of each inline row (form)? I've tried through each form of self.forms in Formset, but I could not access the specific fields of Attribute model (imagine that there are some, I don't want to obfuscate my question with irrelevant data)?
In my example, I would like to have maximum of one Attribute of each type per Product (so that no one puts two or more attributes with the same type associated with one Product).
self.forms[0].cleaned_data
wont work?
I went through
for form in self.forms:
form.instance
And it's ok. Why should cleaned_data be better?