Advanced resources¶
Custom columns¶
In the meta class of the resource you can list either strings or Column
objects under the columns
property.
String keys must always match a field of the model. Column
keys however are free to have any values. Column
objects have the property key
which is going to be used for model property or method lookup. This is very useful to
send values of related models or computed values in the POST
.
Let’s say we have the following models:
class Owner(models.Model):
name = models.CharField(max_length=64)
class SmartItem(models.Model):
owner = models.ForeignKey(Owner)
name = models.CharField(max_length=64)
quantity = models.PositiveIntegerField()
price = models.PositiveIntegerField()
@property
def get_total_price(self):
return self.quantity * self.price
@property
def get_owner(self):
return self.owner.name
Here we’ve created a simple relation between SmartItem
and Owner
so I can show you how to send related values
too but it is super easy.
So there are 2 property methods get_total_price
and get_owner
. To create a resource for this define the
following resource:
from yaat.models import Column
class ModelComputedExampleResource(YaatModelResource):
class Meta:
resource_name = 'model-computed-example'
model = SmartItem
columns = (
Column(key='get_owner', value='Owner'),
'name', 'quantity', 'price',
Column(key='get_total_price', value='Total price')
)
First you have to import the Column
model. Column
objects are going to be mapped to properties or methods of the
model of the resource by their key (key
argument). So in this example when the resource iterates over the queryset
it will get get_owner
property of the model first, then name
, quantity
, prce
fields of the
model, then get_total_price
property of the model at the end. Those properties and methods that are invoked by
the Columns
are called handlers.
We call these columns virtual meaning that you can’t order by their values out of box (because the ORM can’t handle it). You are allowed to create non-virtual columns too but then you must implement the sorting method of those objects.
However the Column
class is a Django model but it’s never stored anywhere unless you mark the resource to be
stateful.
Passing values to handlers¶
Sometimes it is useful to pass an object to a column handler e.g. when you want the model to access the User
instance (request.user
)
Let’s modify the method SmartItem.get_total_price
from the previous example.
def get_total_price(self, currency):
return self.quantity * self.price * currency
It’s quite trivial but let’s say that you want to calculate the Item
‘s total price based on the logged in user’s
currency settings. Note that the method is no longer decorated: you can’t pass values to properties.
To pass the value to the handler you have to override the get_rows
method of the resource class
ModelComputedExampleResource
:
def get_rows(self, *args):
return super().get_rows(*args, currency=request.user.currency)
Here you simply invoke the method from the parent class, pass all arguments and your own value as a keyword argument.
Every handler method receives every passed keyword argument meaning that you have to decide in the handler itself which
arguments you need. Use the **kwargs
argument in this cases.
Note
In the internal implementation when get_rows
gets the model attribute it checks if it is a callable. If it’s
true then it is invoked with all keyword arguments of get_rows
. Otherwise no other processing is made and the
value is stored in the row.
Modifying the row dict¶
In every page each row is described with the following dict structure:
{'id': obj.pk, 'values': cells}
Here obj.pk
is the primary key of the object and cells
is a list
containing the cells of the
given row. This is what yaat expects from you.
If you want to modify this dict (to define a row property) you can do that by overriding the row_hook()
in your
resource like this:
from yaat.resource import YaatModelResource
class ModelExampleResource(YaatModelResource):
class Meta:
resource_name = 'model-example'
model = Item
columns = ('name', 'quantity', 'price')
def row_hook(self, row, obj):
row = super().row_hook(row)
row['is_active'] = obj.is_active
return row
If you need more complex modification you have to override get_rows()
.
Warning
Do not modify or remove the 'id'
and 'values'
keys or the the rendered table will not work at all.
Modifying the table¶
Modifying the whole table is not possible in the resource itself but in a custom serializer class.
By default YaatModelResource
classes use the restify.serializers.DjangoSerializer
serializer.
You should subclass that, override the flatten()
method, and there you can access the dict
describing the required table page. Please don’t remove or modify existing keys and values because
that may make the rendering fail on the client.
However it’s a good place to add properties to the whole table e. g. server time. You can access non-yaat data described in yaat docs.
Stateful columns¶
It is possible to store column states in a persistent storage so you get back the same table when you reload the page. Only column-related things are stored (order, ordering and if it’s hidden). Current page and limit are not.
To make a resource columns stateful simply add the stateful
to its meta class:
class StatefulColumns(YaatModelResource):
class Meta:
stateful = True
That’s it. Any change is going to be saved in your database.
Customizing the column foreign key¶
Yaat’s Column model has a foreign key to settings.AUTH_USER_MODEL
by default. This is what you need in 99.9% of cases.
However sometimes you may want the columns to be accessible from a different model (like from a related model of the User class).
To adjust this set the settings.YAAT_FOREIGN_KEY
key to a string. It is expected to be a dotted pair of the Django app
and the Django model just like for AUTH_USER_MODEL
. See the
docs.
After changing the foreign key you also have to set the settings.YAAT_REQUEST_ATTR
setting because subclasses of
YaatModelResource
depend on request.user
which is likely not an instance of the new foreign key class anymore.
This value is expected to be a single string. The attribute with the same name must exist in the request
object.
Real world example¶
Let’s say we have 2 models, Customer
and User
in an N:M relation through an other model, Membership
, similar to
the Django example
example.
Here the same user should have different column lists depending on which of its membership is active. This means that
columns
should be a property of Membership
instances. To achieve this set the setting:
YAAT_FOREIGN_KEY = 'myapp.Membership'
(Assume Membership
model is in the myapp
Django application)
Since Column.user
is expected to point to a Membership
instance, and request.user
is still a User
, you
have to add the active Membership
object to each request. It’s the easiest using a middleware. Say the Membership
is accessible through request.member
then set the setting to this:
YAAT_REQUEST_ATTR = 'member'
Note
Keep in mind that the name of the property Column.user
stays the same if you override YAAT_FOREIGN_KEY
but it points to a different type of object then.
Warning
Changing YAAT_FOREIGN_KEY
has a huge impact just like changing AUTH_USER_MODEL
. Be sure to set this value
before applying your migrations the very first time. If you set this value later the real foreign key in your
database will still point to the old table.
Stateful table pages¶
Django-yaat can store yaat’s last limit
and offset
values in the authenticated user’s session so you can
send the exact same page every time the user arrives to the same table. This is useful for cases when the user
navigates away and back to the same table often.
Simply add the stateful_init
to the meta class of the resource:
class StatefulInit(YaatModelResource):
class Meta:
stateful_init = True
You can combine this with stateful
of course.
Utility methods¶
There are a few utility methods that may help you in some rare cases.
-
class
YaatModelResource
¶ -
classmethod
invalidate_column_cache
(user)¶ This method forces to invalidate the given user’s
Column
. This can help you if you add a newColumn
object on the fly and you want to show it immediately.Argument
user
is expected to be an instance ofAUTH_USER_MODEL
class orYAAT_FOREIGN_KEY
class if that’s specified.
-
classmethod
-
class
YaatValidatorForm
¶ -
invalidate_state
()¶ Every
YaatModelResource
(and its subclasses) gets the attributeself.validator_form
when theYaatModelResource.common(request, *args, **kwargs)
method is invoked. This form is used for validating the received data during paging but also for creating the initial data. If you set the resource meta tostateful_init = True
the form keeps its last received data as the initialization state. If you’d like to drop this state call this method.
-