How To: Configure IDs and Labels
wq’s built-in model registration system makes it possible to quickly scaffold an application with default list/edit/detail views for each model. However, it is almost always necessary to customize the label generated for each record stored in the database. Additionally, it is often desirable to override the identifier used to specify each record, for example to create friendly slug-based urls.
These two options are configured somewhat differently in wq, but since they are so closely related it makes sense to discuss both in this guide.
Note that it is also possible to configure the verbose names used to describe each model as a whole, for example in the title of list views. This guide focuses only on record-level identifiers - see How To: Configure Route Names and URLs for collection-level configuration.
Record Labels
Suppose you have a model named Group
that is registered with wq.db.
# myapp/models.py
class Group(models.Model):
name = models.TextField()
# myapp/rest.py
from wq.db import rest
from .models import Group
rest.router.register_model(
Group,
fields="__all__"
)
As you start populating this model with data, you may notice that the records have generic names like “Group Object (1)” and “Group Object (2)”. To fix this, you can use Django’s built-in __str__()
support to give each record a label:
class Group(models.Model):
name = models.TextField()
def __str__(self):
return self.name
The value of __str__
is surfaced as "label"
, a read only field in the default ModelSerializer. So the JSON output for a group object would be something like be this:
{
// Read-only attributes
"id": 1,
"label": "First Group",
// Modifiable attributes (will appear in edit form)
"name": "First Group"
}
This will work fine as long as each record is synced to the server instantly so it can return the label. However, if you plan to be offline for extended periods of time you will also want to configure offline labels.
Offline Record Labels
Record labels can be generated offline by setting wq_label_template
on the model. (This setting would ideally go on the Meta
class, but it’s not a standard Django attribute.) The value of wq_label_template
is exported in the wq configuration object as label_template
.
class Group(models.Model):
name = models.TextField()
wq_label_template = ''
def __str__(self):
return self.name
To reduce redundancy, you can import LabelModel
which already implements __str__()
to render the contents of wq_label_template
:
from wq.db.patterns.models import LabelModel
class Group(LabelModel):
name = models.TextField()
wq_label_template = ''
LabelModel
’s default template is ""
, so you could even leave out wq_label_template
in this specific case.
Advanced Offline Record Labels
If a Mustache template is not sufficient, you can also implement the offline template as a JavaScript function. Since the data/config.js generated by wq.db is effectively JSON-only, a JavaScript template should be defined elsewhere in the application code.
// app/js/myproject.js (or app/src/config.js)
import wq from './wq.js';
import config from './data/config.js';
config.pages.group.label_template = group => group.name;
wq.init(config);
Record IDs
By default, wq.db will use Django’s automatic id
AutoField as the record identifier in URL lookups and in JSON sent to the client. In many use cases it is desirable to use a different value. While the default field can be overridden directly in the model definition (by explicitly defining a field with primary_key=True
), the wq-recommended approach is to define a second unique field on the model and register it with wq.db as the “lookup” field.
# myapp/models.py
class Group(LabelModel):
# id = models.AutoField(primary_key=True) # Implicit
code = models.SlugField(unique=True)
name = models.TextField()
# myapp/rest.py
from wq.db import rest
from .models import Group
rest.router.register_model(
Group,
lookup="code",
fields="__all__"
)
The lookup value is always mapped to “id” in the JSON output, so wq.app (and specifically the ORM) can treat it as the primary key. While this could also be accomplished with a custom ModelSerializer class, doing it via register_model()
also ensures that the corresponding ModelViewSet can handle detail URLs using the same identifier. In addition, the lookup
setting is automatically propagated to the serialization of any foreign keys in related models.
{
// Read-only attributes
"id": "first-group",
"label": "First Group",
// Modifiable attributes (will appear in edit form)
"code": "first-group",
"name": "First Group"
}
As can be seen, the fact that
id
is essentially an alias forcode
means that it is indirectly writable through the serializer. This can lead to unexpected results - for example, any synced change to the existing code value will temporarily result in two copies of the record in the local ORM until the page is refreshed and the record with the oldid
is purged. We recommend makingcode
read-only except for new records, or at least limiting changes to administrators (perhaps with a custom input type).
Multi-Column IDs
When using wq.db with the Django Natural Keys package, it is possible to define a composite "id"
attribute that comprises multiple database fields. To do so, define lookup
as "natural_key_slug"
as in the example below.
# myapp/models.py
from natural_keys import NaturalKeyModel
class Event(NaturalKeyModel):
prefix = models.SlugField()
date = models.DateField()
class Meta:
unique_together = [('prefix', 'date')]
# myapp/rest.py
from wq.db import rest
from .models import Event
rest.router.register_model(
Event,
lookup="natural_key_slug",
fields="__all__"
)
See Django Natural Keys’ natural_key_slug
documentation for more info.
Offline Record IDs (Automatic)
One common use for custom id fields is to facilitate offline usage, since it is not possible to know what identifier the server will assign until the record is synced. (In other projects, this use case is commonly handled by using a UUID field.) wq automatically generates offline record ids, regardless of the lookup
setting or any custom primary key on the model. These offline “outbox IDs” are automatically swapped for the actual "id"
value returned from the server during sync. If there are any records for related models, they will not be synced until the parent model instance has its server-assigned identifier.
Thus, it is not necessary to define a template or function to generate offline record identifiers.