Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions docs/docs/user-guide/data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ This is a custom type implemented in Mathesar's UI. To enable this data type, yo

Learn more about [Mathesar's file feature](./files.md).

### User

- **PostgreSQL types**
- `integer`

This is a custom type implemented in Mathesar's UI. User columns store Mathesar user IDs, allowing you to reference Mathesar users directly in your database tables.

Learn more about [working with user columns](./user-type.md).

## Other PostgreSQL types

Mathesar has rudimentary support for other PostgreSQL types such as: `array`, `bytea`, `point`, `line`, `lseg`, `box`, `path`, `path`, `polygon`, `circle`, `cidr`, `inet`, `macaddr`, `macaddr8`, `bit`, `bit varying`, `tsquery`, `tsvector`, `json`, `jsonb`, `xml`, `pg_lsn`, `pg_snapshot`, `txid_snapshot`, `int4range`, `int8range`, `numrange`, `tsrange`, `tstzrange`, `daterange`.
Expand Down
115 changes: 115 additions & 0 deletions docs/docs/user-guide/user-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Working with the user data type

Mathesar's **user data type** allows you to store references to Mathesar users directly in your database tables. This makes it easy to track who created records, who is assigned to tasks, or who last edited a row.

- User columns store Mathesar user IDs (integer values).
- Users are displayed using their username, display name, or email address (configurable).
- You can select users from a searchable list when editing cells.
- User columns support default values, including automatically tracking who last edited a record.

## Adding user columns

![Adding an "Instructor" user column to an "Equipment Training" table](../assets/images/user-type/adding-user-column.png)
/// caption
A library administrator adding an "Instructor" column to track which staff member conducted each training session.
///

Adding a user column works just like adding any other column type in Mathesar:

1. Open the table where you want to add a user column.
2. Click the "+" icon **Add Column**.
3. Choose **User** as the column type and name the column.
4. Save your changes.

Once the user column is created, you'll be able to select a user for each cell directly from the Mathesar UI.

## Selecting users

To select a user for a user cell, click on the cell and use the user selection dialog:

![Selecting a user from the searchable list](../assets/images/user-type/selecting-user.png)
/// caption
Clicking on a user cell opens a searchable dropdown list of all Mathesar users.
///

1. Click on an empty user cell (or double-click a cell with an existing value).
2. A searchable list of Mathesar users will appear.
3. Type to search for users by their username, display name, or email.
4. Click on a user to select them.

You can also clear a user value by selecting the cell and removing the selection.

## Configuring display options

User columns can display users using different fields. You can configure which field to use in the column inspector:

![Configuring the display field for a user column](../assets/images/user-type/display-options.png)
/// caption
In the column inspector, you can choose whether to display users by their username, display name, or email address.
///

1. Open the table containing the user column.
1. Click on the column header to open the column inspector.
1. In the **Display Options** section, find **Field to represent each user**.
1. Choose from:
- **Username** - The user's login username (default)
- **Display Name** - The user's full name
- **Email** - The user's email address

The selected display field will be used throughout Mathesar when showing this column's values.

## Setting default values

User columns support three default value options, which you can configure in the column inspector's **Default Value** section:

![Default value options for a user column](../assets/images/user-type/default-values.png)
/// caption
User columns support three default value modes: no default, auto-set to editor, or set a specific default user. When "auto-set" is chosen, the user column is not editable.
///

### No default

The column has no default value. Users must manually select a value for each row.

### Auto-set to editor

Automatically sets the column to the user who last edited the record. This is useful for tracking who made changes to records.

!!! info "Auto-set columns are read-only"
When a column is set to "Auto-set to editor", it becomes non-editable in the Mathesar UI. The value is automatically updated whenever a record is modified through Mathesar, including via form submission.

### Set a default user

Sets a specific user as the default value for new rows. When you create a new record, this column will automatically be populated with the selected user.

To set a default user:

1. Open the column inspector for the user column.
1. In the **Default Value** section, select **Set a default user**.
1. Choose the user from the user selection dialog.
1. Save your changes.

## Limitations

### User columns cannot be used in forms

User type columns cannot be added to [forms](./forms.md) because they require authentication to access the list of Mathesar users. Forms can be submitted anonymously, and user fields would not function correctly in that context.

!!! warning "Forms restriction"
If you try to add a user column to a form, you'll see an error message. User columns are automatically excluded from form field options.

Note that columns with "Auto-set to editor" enabled will still be automatically populated when form submissions are processed, even though the field cannot be included in the form itself.

## Use cases

User columns are useful for:

- **Tracking ownership**: Record who created or owns a record (e.g., "Created By", "Assigned To").
- **Audit trails**: Track who last modified a record using "Auto-set to editor".
- **Task assignment**: Assign tasks or items to specific users.
- **Approval workflows**: Track who approved or reviewed records.

For example, in a **Projects** table, you might have:

- an **Assigned To** column set to a project manager's Mathesar user by default
- a **Last Modified By** column set to "Auto-set to editor" to track recent changes
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ nav:
- File data type: user-guide/files.md
- Form builder: user-guide/forms.md
- Data Explorer: user-guide/data-explorer.md
- User data type: user-guide/user-type.md
- Data management:
- Importing data: user-guide/importing-data.md
- Exporting data: user-guide/exporting-data.md
Expand Down
36 changes: 36 additions & 0 deletions mathesar/migrations/0010_columnmetadata_user_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated manually

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("mathesar", "0009_columnmetadata_file_backend"),
]

operations = [
migrations.AddField(
model_name="columnmetadata",
name="user_type",
field=models.BooleanField(default=False, null=True),
),
migrations.AddField(
model_name="columnmetadata",
name="user_display_field",
field=models.CharField(
choices=[
("full_name", "full_name"),
("email", "email"),
("username", "username"),
],
max_length=50,
null=True,
),
),
migrations.AddField(
model_name="columnmetadata",
name="user_last_edited_by",
field=models.BooleanField(default=False, null=True),
),
]
7 changes: 7 additions & 0 deletions mathesar/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,13 @@ class ColumnMetaData(BaseModel):
duration_max = models.CharField(max_length=255, null=True)
display_width = models.PositiveIntegerField(null=True)
file_backend = models.CharField(max_length=255, null=True)
user_type = models.BooleanField(default=False, null=True)
user_display_field = models.CharField(
choices=[("full_name", "full_name"), ("email", "email"), ("username", "username")],
max_length=50,
null=True
)
user_last_edited_by = models.BooleanField(default=False, null=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this name really confusing. What about track_editing_user?


class Meta:
constraints = [
Expand Down
19 changes: 19 additions & 0 deletions mathesar/rpc/columns/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class ColumnMetaDataRecord(TypedDict):
duration_max: The largest unit for displaying durations.
display_width: The pixel width of the column
file_backend: The name of a backend for storing file attachments.
user_type: Whether this column stores Mathesar user IDs.
user_display_field: Which user field to display for user columns (full_name, email, or username).
user_last_edited_by: Whether this column automatically tracks the last user who edited the record.
"""
database_id: int
table_oid: int
Expand All @@ -50,6 +53,10 @@ class ColumnMetaDataRecord(TypedDict):
duration_min: Optional[str]
duration_max: Optional[str]
display_width: Optional[int]
file_backend: Optional[str]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, nice catch

user_type: Optional[bool]
user_display_field: Optional[Literal["full_name", "email", "username"]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If user_display_field is optional, I guess I don't see why user_type is needed. If this is non-null, then display the column as specified, otherwise, just display as an integer.

If you really really want to keep user_type, I don't think that should be optional since it's always either true or false. Just use false for the default.

user_last_edited_by: Optional[bool]

@classmethod
def from_model(cls, model):
Expand All @@ -72,6 +79,8 @@ def from_model(cls, model):
duration_max=model.duration_max,
display_width=model.display_width,
file_backend=model.file_backend,
user_type=model.user_type,
user_display_field=model.user_display_field or "username",
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think user_last_edited_by is missing here.



Expand All @@ -96,6 +105,9 @@ class ColumnMetaDataBlob(TypedDict):
duration_max: The largest unit for displaying durations.
display_width: The pixel width of the column.
file_backend: The name of a backend for storing file attachments.
user_type: Whether this column stores Mathesar user IDs.
user_display_field: Which user field to display for user columns (full_name, email, or username).
user_last_edited_by: Whether this column automatically tracks the last user who edited the record.
"""
attnum: int
bool_input: Optional[Literal["dropdown", "checkbox"]]
Expand All @@ -112,6 +124,10 @@ class ColumnMetaDataBlob(TypedDict):
duration_min: Optional[str]
duration_max: Optional[str]
display_width: Optional[int]
file_backend: Optional[str]
user_type: Optional[bool]
user_display_field: Optional[Literal["full_name", "email", "username"]]
user_last_edited_by: Optional[bool]

@classmethod
def from_model(cls, model):
Expand All @@ -132,6 +148,9 @@ def from_model(cls, model):
duration_max=model.duration_max,
display_width=model.display_width,
file_backend=model.file_backend,
user_type=model.user_type,
user_display_field=model.user_display_field,
user_last_edited_by=model.user_last_edited_by,
)


Expand Down
5 changes: 4 additions & 1 deletion mathesar/rpc/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,7 @@ def submit(*, form_token: str, values: dict, **kwargs) -> None:
form_token: The unique token of the form.
values: A dict describing the values to insert.
"""
return submit_form(form_token, values)
from modernrpc.core import REQUEST_KEY
request = kwargs.get(REQUEST_KEY)
user = request.user if request else None
Comment on lines +417 to +419
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Why are you reimporting REQUEST_KEY?
  • This isn't going to be called without a request, right? If not, there's way too much logic here.

return submit_form(form_token, values, user)
Loading
Loading