Skip to main content

Overview

QFieldCloud’s collaboration system allows multiple users to work together on projects with role-based access control. The ProjectCollaborator model manages user permissions and determines what actions each team member can perform.

Collaboration Model

The ProjectCollaborator model is defined in core/models.py:1981 and creates a many-to-many relationship between projects and users.

Key Fields

  • project: Foreign key to the Project
  • collaborator: Foreign key to User (Person or Team)
  • role: Collaboration role (Admin, Manager, Editor, Reporter, Reader)
  • is_incognito: Whether collaborator is hidden in UI and billing
  • created_by: User who added the collaborator
  • created_at: Timestamp when added
  • updated_by: User who last updated the collaborator
  • updated_at: Timestamp of last update

Collaboration Roles

QFieldCloud defines five collaboration roles with varying permission levels:

Admin

Full project control:
  • Manage collaborators
  • Delete the project
  • Update project settings
  • Upload/download files
  • Create and apply deltas
  • Trigger packaging jobs

Manager

Project management without deletion:
  • Manage collaborators (cannot remove admins)
  • Update project settings
  • Upload/download files
  • Create and apply deltas
  • Trigger packaging jobs

Editor

Full editing capabilities:
  • Upload/download files
  • Create and apply deltas
  • Trigger packaging jobs
  • Cannot modify project settings or collaborators

Reporter

Data submission only:
  • Create deltas (submit field data)
  • Download project files
  • Cannot upload files directly
  • Cannot apply deltas

Reader

Read-only access:
  • Download project files
  • View project information
  • Cannot make any changes

Adding Collaborators

API Endpoint

POST /api/v1/collaborators/{projectid}/

Request Body

{
  "collaborator": "username",
  "role": "editor"
}

Example with cURL

curl -X POST https://app.qfield.cloud/api/v1/collaborators/{projectid}/ \
  -H "Authorization: Token YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "collaborator": "field_worker_1",
    "role": "reporter"
  }'

Validation Rules

When adding collaborators, the following rules are enforced:
  1. Cannot add project owner - The owner is implicitly an admin
  2. Organization membership required - For organization-owned projects, collaborators must be organization members
  3. No duplicate collaborators - Each user can only be added once per project
  4. Subscription limits - Private projects enforce max collaborator limits based on the owner’s plan
See core/models.py:2052 for validation logic.

Organization-Owned Projects

For projects owned by an organization:

Member Requirements

if project.owner.is_organization:
    organization = Organization.objects.get(pk=project.owner.pk)
    if collaborator.is_person:
        # Must be organization member or owner
        if not (
            organization.members.filter(member=collaborator).exists()
            or collaborator == organization.organization_owner
        ):
            raise ValidationError("Cannot add non-member as collaborator")

Team Collaborators

Organizations can add entire teams as collaborators:
{
  "collaborator": "@organization_name/team_name",
  "role": "editor"
}
All team members inherit the team’s role on the project.

Managing Collaborators

List Collaborators

GET /api/v1/collaborators/{projectid}/
Returns all collaborators with their roles and metadata.

Update Collaborator Role

PATCH /api/v1/collaborators/{projectid}/{username}/
{
  "role": "manager"
}

Remove Collaborator

DELETE /api/v1/collaborators/{projectid}/{username}/

Permissions System

The permissions system checks multiple factors:

Role Origin

Users can have project roles from multiple sources:
class RoleOrigins(models.TextChoices):
    PROJECTOWNER = "project_owner"
    ORGANIZATIONOWNER = "organization_owner"
    ORGANIZATIONADMIN = "organization_admin"
    COLLABORATOR = "collaborator"
    TEAMMEMBER = "team_member"
    PUBLIC = "public"
The highest permission level applies when a user has multiple roles.

Permission Checks

Key permission functions in core/permissions_utils.py:
  • can_create_collaborators(user, project) - Check if user can add collaborators
  • can_update_collaborators(user, project) - Check if user can modify collaborators
  • can_delete_collaborators(user, project) - Check if user can remove collaborators
  • can_read_collaborators(user, project) - Check if user can view collaborator list
See core/views/collaborators_views.py:14 for permission implementation.

Subscription Limits

Collaborator Validation

Private projects enforce collaborator limits based on the owner’s subscription:
max_premium_collaborators_per_private_project_q = Q(
    project__owner__useraccount__current_subscription_vw__plan__max_premium_collaborators_per_private_project=V(-1)
) | Q(
    project__owner__useraccount__current_subscription_vw__plan__max_premium_collaborators_per_private_project__gte=count
)
When max_premium_collaborators_per_private_project is:
  • -1: Unlimited collaborators
  • n > 0: Maximum of n collaborators
See core/models.py:1930 for validation queryset.

Direct Collaborators

Get non-owner collaborators:
@property
def direct_collaborators(self) -> ProjectCollaboratorQueryset:
    if self.owner.is_organization:
        exclude_pks = [self.owner.organization.organization_owner_id]
    else:
        exclude_pks = [self.owner_id]
    
    return (
        self.collaborators.skip_incognito()
        .filter(collaborator__type=User.Type.PERSON)
        .exclude(collaborator_id__in=exclude_pks)
    )
See core/models.py:1846.

Incognito Collaborators

Special flag for support users:
is_incognito = models.BooleanField(
    default=False,
    help_text="Collaborators marked as incognito work normally but are not listed in the UI or counted in subscription limits"
)
Incognito collaborators:
  • Function normally with assigned role
  • Hidden from collaborator lists in UI
  • Not counted toward subscription limits
  • Used for OPENGIS.ch support access

Project Roles View

The ProjectRolesView is a database view that aggregates user roles:
class ProjectRolesView(models.Model):
    user = models.ForeignKey(User, related_name="project_roles")
    project = models.ForeignKey(Project, related_name="user_roles")
    name = models.CharField(choices=ProjectCollaborator.Roles.choices)
    origin = models.CharField(choices=ProjectQueryset.RoleOrigins.choices)
    is_incognito = models.BooleanField()
This view powers efficient permission queries without complex joins. See core/models.py:2081.

Best Practices

Role Selection Guidelines

  • Admin: Project leads, IT administrators
  • Manager: Field coordinators, data managers
  • Editor: GIS analysts, data collectors with upload rights
  • Reporter: Field workers using QField mobile
  • Reader: Stakeholders needing view-only access

Security Considerations

  1. Principle of least privilege - Grant minimum necessary permissions
  2. Regular audits - Review collaborator lists periodically
  3. Organization structure - Use teams for consistent permissions
  4. Public projects - Remember all authenticated users can read public projects

Performance

  • Use skip_incognito() when counting collaborators for billing
  • Query with select_related("collaborator") to avoid N+1 queries
  • Filter invalid collaborators with validated(skip_invalid=True)

API Reference

List All Collaborators

GET /api/v1/collaborators/{projectid}/
Response:
[
  {
    "collaborator": "user1",
    "role": "editor",
    "created_at": "2024-01-15T10:30:00Z",
    "created_by": "admin_user"
  }
]

Get Collaborator Details

GET /api/v1/collaborators/{projectid}/{username}/

Update Role

PUT /api/v1/collaborators/{projectid}/{username}/

Remove Collaborator

DELETE /api/v1/collaborators/{projectid}/{username}/

Build docs developers (and LLMs) love