Custom columns in django_tables2 - django

I've had a search around for this but haven't had much luck so looking for a bit of help. I'm trying to add some extra columns to a table defined by a model, using function definitions in the model. Here's what my code looks like now:
# models.py
class MyModel(models.Model):
my_field = models.TextField()
def my_function(self):
# Return some calculated value based on the entry
return my_value
# tables.py
class MyTable(tables.Table):
my_extra_column = tables.Column(....)
class Meta:
model = MyModel
# views.py
table = MyTable(MyModel.objects.all())
RequestConfig(request).configure(table)
return render(request, ....)
My question is can I access my_function in the entries passed to MyTable so I can show the result of my_function in the custom my_extra_column column? I assume I need to be using accessors, but I can't see how I can access the queryset data using this. Thanks!

I figured it out in the end, it was actually not too hard after all :)
So using my example above, in order to add a custom column using a function in the associated model you just use accessors ...
# models.py
class MyModel(models.Model):
my_field = models.TextField()
my_field_2 = models.IntegerField()
def my_function(self):
# Return some calculated value based on the entry
return my_value
# tables.py
class MyTable(tables.Table):
my_extra_column = tables.Column(accessor='my_function',
verbose_name='My calculated value')
class Meta:
fields = ['my_field', 'my_field_2', 'my_extra_column']
model = MyModel
The trouble comes if and when you want to be able to sort this data, because the function won't translate into any valid field in MyModel. So you could either disable sorting on this column using ordering=False or specify a set using order_by=('field', 'field2')

Related

Django Rest. Ordering by calculated model field

I am stuck with ordering on calculated field.
Let's say my model looks like:
class Foo(models.Model):
fieldA = models.CharField()
fieldB = models.CharField()
#property
def calculatedField(self):
return someFunc(fieldA)
Now I wan't my ViewSet to be able to apply ordering to calculatedField, so I have following code in there:
class SomeViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
...
ordering_fields = ('calculatedField',)
...
But when I try to apply to order using query parameters like
Method GET /someEndpoint/?ordering=calculatedField
I get the following error
Cannot resolve keyword 'calculatedField' into the field. Choices are: ...
Is there a way to apply to order to calculatedField? Thanks
you have to annotate the extra field
class SomeViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
...
ordering_fields = ('calculatedField',)
def get_queryset(self):
return self.queryset.annotate(other function)

Django Rest Framework – Custom Hyperlink field in serializer

How can I add a custom hyperlink field in a serializer? I would like to have a hyperlink field in my serializer that has query params in it. Since there is no way to pass query params from HyperlinkedRelatedField or HyperlinkedIdentityField as far as I know, I've tried using a SerializerMethodField. However, this only serializes to a string, and is not a clickable URL when I visit the API through my browser. My code looks something like this:
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('url', 'custom_field')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view'),
urllib.urlencode({'param': 'foo'})
)
return result
Also, I am having trouble understanding the difference between a HyperlinkedRelatedField and a HyperlinkedIdentityField, so a brief explanation would be appreciated.
This should do the trick:
from rest_framework.reverse import reverse
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('url', 'custom_field')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view', args=[obj.id], request=self.context['request']),
'param=foo'
)
return result
The reverse function in rest_framework takes a view name (whatever view you'd like to link to), either an args list (the object id, in this case) or kwargs, and a request object (which can be accessed inside the serializer at self.context['request']). It can additionally take a format parameter and any extra parameters (as a dictionary) that you want to pass to it.
The reverse function then builds a nice, fully-formed URL for you. You can add query params to it by simply adding as many ?{}&{}&{} to your result variable and then filling in the series of query params beneath the 'param=foo' inside your format function with whatever other params you want.
The HyperlinkedIdentityField is used on the object itself that is being serialized. So a HyperlinkedIdentifyField is being used in place of your primary key field on MyModel because you are using a HyperlinkedModelSerializer which creates a HyperlinkedIdentityField for the pk of the object itself being serialized.
The HyperlinkedRelatedField is used to define hyperlinked relationships to RELATED objects. So if there were a MySecondModel with a foreign key relationship to MyModel and you wanted to have a hyperlink on your MyModel serializer to all the related MySecondModel objects you would use a HyperlinkedRelatedField like so (remember to add the new field to your fields attribute in Meta):
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
mysecondmodels = serializers.HyperlinkedRelatedField(
many=True
read_only=True,
view_name='mysecondmodel-detail'
)
class Meta:
model = MyModel
fields = ('url', 'custom_field', 'mysecondmodels')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view', args=[obj.id], request=self.context['request']),
'param=foo'
)
return result
If it were a OneToOneField rather than ForeignKey field on MySecondModel then you would set many=False.
Hope this helps!

Django-tables2: how to order_by accessor

Say we have a column like:
num_member = tables.Column(accessor = 'members.count', verbose_name = 'number of members' )
When I tried to sort this in the template, it raises:
Field Error: Cannot resolve keyword u'count' into field
I read the document and it says we can use order_by by passing in some sort of accessor, but how exactly do we do this please?
For function like Model's property method, you can access it directly using accessor. For example:
Class MyModel(models.Model):
data= models.CharField(max_length=255)
#property
def print_function(self):
return 'hello world'
#Table class
class MyTable(tables.Table):
data= tables.Column(accessor='print_function')
class Meta:
model = MyModel
fields = ('data')
Using the above method, you can show different kinds of data in table using accessor:
Class SomeModel(models.Model):
some_data= models.CharField(max_length=255)
data= models.ManyToManyField(MyModel)
#property
def count_function(self):
some_data= self.data.objects.count() #returns count of the objects
return some_data
#Table class
class SomeTable(tables.Table):
data= tables.Column(accessor='count_function')
class Meta:
model = SomeModel
fields = ('data')
And accessor can be used for directly accessing related foreignkey model's field value like:
Class SomeModel(models.Model):
somedata= models.ForeignKey(MyModel)
#Table class
class MyTable(tables.Table):
data= tables.Column(accessor='somedata.data')
class Meta:
model = SomeModel
fields = ('data')
EDIT
Lets give an example of how order_by can be used:
#Class Based view, I am subclassing ListView and SingleTableView here for example
class MyView(ListView, SingleTableView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['table'].order_by = '-last_updated' #last_updated is a datetimefield in model
return context
In the above code, what I have done is that, I am changing the order of the table data in context which will later be rendered in template.
Fairly old question, however, I have been confronted with the same problem today: I couldn't order my tables if the accessor was a property (or 0-argument-method) and not a model field.
After not finding anything in the docs and inspecting the source code, it turned out that tables2 will pass the ordering to the database if its data is a QuerySet, but otherwise it will do a Python list sort with an appropriate key:
# django_tables2/tables.py -> class TableData
def order_by(self, aliases):
# ...
if hasattr(self, "queryset"):
translate = lambda accessor: accessor.replace(Accessor.SEPARATOR, QUERYSET_ACCESSOR_SEPARATOR)
if accessors:
self.queryset = self.queryset.order_by(*(translate(a) for a in accessors))
else:
self.list.sort(key=OrderByTuple(accessors).key)
I assume that this can not be trivially solved by using a try-except instead of the if-else because an exception would only be raised once the queryset is evaluated which only happens later.
Solution: whenever your sort-parameter is not a model field, turn the QuerySet into a list before handing it to the table. For many cases in django, this will be as simple as overriding get_queryset:
def get_queryset(self):
qs = super(ViewName, self).get_queryset()
return list(qs)
This should work best if your accessor is a cached_property on the model of your table, e.g.:
from django.utils.functional import cached_property
#cached_property
def member_count(self):
# do the heavy stuff here in the model
return whatever
Then, in the table:
num_member = tables.Column(
accessor='members_count',
verbose_name='number of members'
)

Adding custom data to django model field

I'd like to add some info to a model field to use at form rendering time. My real model has about 15 values of varying field types (adding and removing as I dev), and it does almost everything I need, so I'd rather not create custom model fields for all of them.
I'd like to do something like this:
from django.db import models
class MyModel(models.Model):
cost = models.DecimalField(max_digits=5,
decimal_places=2,
custom_info= {'glyph': 'glyphicon glyphicon-usd' }
)
And then in my form template use that glyph much like I'd use a verbose_name or help_text.
Something I learned from a post just the other day. Will defining the custom information on the form instead of the model work?
When you define formfield_callback on a forms.ModelForm it will iterate over the form fields and you can manipulate them. This comes in handy when you need to add a css class to widgets and don't want to explicitly override the field. Now you only need to put formfield_callback = modify_form_field on any forms.ModelForm where you want the custom_info to show up.
from django.db import models
def add_glyphicons(model_field):
form_field = model_field.formfield()
if isinstance(model_field, models.IntegerField):
form_field.custom_info = {'glyph': 'glyphicon glyphicon-usd'}
elif isinstance(model_field, models.CharField):
form_field.custom_info = {'glyph': 'glyphicon glyphicon-yen'}
return form_field
class MyModel(models.Model):
formfield_callback = add_glyphicons
class Meta:
model = MyModel
class MyOtherModel(models.Model):
formfield_callback = add_glyphicons
class Meta:
model = MyOtherModel

Django REST Framework: adding additional field to ModelSerializer

I want to serialize a model, but want to include an additional field that requires doing some database lookups on the model instance to be serialized:
class FooSerializer(serializers.ModelSerializer):
my_field = ... # result of some database queries on the input Foo object
class Meta:
model = Foo
fields = ('id', 'name', 'myfield')
What is the right way to do this? I see that you can pass in extra "context" to the serializer, is the right answer to pass in the additional field in a context dictionary?
With that approach, the logic of getting the field I need would not be self-contained with the serializer definition, which is ideal since every serialized instance will need my_field. Elsewhere in the DRF serializers documentation it says "extra fields can correspond to any property or callable on the model". Are "extra fields" what I'm talking about?
Should I define a function in Foo's model definition that returns my_field value, and in the serializer I hook up my_field to that callable? What does that look like?
Happy to clarify the question if necessary.
I think SerializerMethodField is what you're looking for:
class FooSerializer(serializers.ModelSerializer):
my_field = serializers.SerializerMethodField('is_named_bar')
def is_named_bar(self, foo):
return foo.name == "bar"
class Meta:
model = Foo
fields = ('id', 'name', 'my_field')
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
You can change your model method to property and use it in serializer with this approach.
class Foo(models.Model):
. . .
#property
def my_field(self):
return stuff
. . .
class FooSerializer(ModelSerializer):
my_field = serializers.ReadOnlyField(source='my_field')
class Meta:
model = Foo
fields = ('my_field',)
Edit: With recent versions of rest framework (I tried 3.3.3), you don't need to change to property. Model method will just work fine.
With the last version of Django Rest Framework, you need to create a method in your model with the name of the field you want to add. No need for #property and source='field' raise an error.
class Foo(models.Model):
. . .
def foo(self):
return 'stuff'
. . .
class FooSerializer(ModelSerializer):
foo = serializers.ReadOnlyField()
class Meta:
model = Foo
fields = ('foo',)
if you want read and write on your extra field, you can use a new custom serializer, that extends serializers.Serializer, and use it like this
class ExtraFieldSerializer(serializers.Serializer):
def to_representation(self, instance):
# this would have the same as body as in a SerializerMethodField
return 'my logic here'
def to_internal_value(self, data):
# This must return a dictionary that will be used to
# update the caller's validation data, i.e. if the result
# produced should just be set back into the field that this
# serializer is set to, return the following:
return {
self.field_name: 'Any python object made with data: %s' % data
}
class MyModelSerializer(serializers.ModelSerializer):
my_extra_field = ExtraFieldSerializer(source='*')
class Meta:
model = MyModel
fields = ['id', 'my_extra_field']
i use this in related nested fields with some custom logic
My response to a similar question (here) might be useful.
If you have a Model Method defined in the following way:
class MyModel(models.Model):
...
def model_method(self):
return "some_calculated_result"
You can add the result of calling said method to your serializer like so:
class MyModelSerializer(serializers.ModelSerializer):
model_method_field = serializers.CharField(source='model_method')
p.s. Since the custom field isn't really a field in your model, you'll usually want to make it read-only, like so:
class Meta:
model = MyModel
read_only_fields = (
'model_method_field',
)
If you want to add field dynamically for each object u can use to_represention.
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ('id', 'name',)
def to_representation(self, instance):
representation = super().to_representation(instance)
if instance.name!='': #condition
representation['email']=instance.name+"#xyz.com"#adding key and value
representation['currency']=instance.task.profile.currency #adding key and value some other relation field
return representation
return representation
In this way you can add key and value for each obj dynamically
hope u like it
This worked for me.
If we want to just add an additional field in ModelSerializer, we can
do it like below, and also the field can be assigned some val after
some calculations of lookup. Or in some cases, if we want to send the
parameters in API response.
In model.py
class Foo(models.Model):
"""Model Foo"""
name = models.CharField(max_length=30, help_text="Customer Name")
In serializer.py
class FooSerializer(serializers.ModelSerializer):
retrieved_time = serializers.SerializerMethodField()
#classmethod
def get_retrieved_time(self, object):
"""getter method to add field retrieved_time"""
return None
class Meta:
model = Foo
fields = ('id', 'name', 'retrieved_time ')
Hope this could help someone.
class Demo(models.Model):
...
#property
def property_name(self):
...
If you want to use the same property name:
class DemoSerializer(serializers.ModelSerializer):
property_name = serializers.ReadOnlyField()
class Meta:
model = Product
fields = '__all__' # or you can choose your own fields
If you want to use different property name, just change this:
new_property_name = serializers.ReadOnlyField(source='property_name')
As Chemical Programer said in this comment, in latest DRF you can just do it like this:
class FooSerializer(serializers.ModelSerializer):
extra_field = serializers.SerializerMethodField()
def get_extra_field(self, foo_instance):
return foo_instance.a + foo_instance.b
class Meta:
model = Foo
fields = ('extra_field', ...)
DRF docs source
Even though, this is not what author has wanted, it still can be considered useful for people here:
If you are using .save() ModelSerializer's method, you can pass **kwargs into it. By this, you can save multiple dynamic values.
i.e. .save(**{'foo':'bar', 'lorem':'ipsum'})
Add the following in serializer class:
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['package_id'] = "custom value"
return representation