F
Filament•16mo ago
Laurens

Disable tenant ownership relation

In a Jetstream based app we're using the Teams setup. In our app however, not all models are strictly separated per team. Some models are always available for all teams, some are filtered if they are associated to at least one team, and others are always associated to teams. In Filament v2, we already implemented the teams by applying global scopes on models which should vary per team. Relation managers then handle the associations between teams where required, else the create / edit pages ensure (new) models are synced to the correct team. As we couldn't switch between the active team from Filament before (and only through the Jetstream front-end), we currently didn't have a team switcher. After upgrading to Filament v3 after all third party packages have been updated, I checked out the tenancy docs to see if we could hook in onto the new Multi-tenancy setup. Tenancy worked straight out of the box by implementing the interface and adding the methods from the docs:
public function getTenants(Panel $panel): \Illuminate\Support\Collection
{
return $this->teams;
}

public function canAccessTenant(\Illuminate\Database\Eloquent\Model $tenant): bool
{
return $this->teams->contains($tenant);
}
public function getTenants(Panel $panel): \Illuminate\Support\Collection
{
return $this->teams;
}

public function canAccessTenant(\Illuminate\Database\Eloquent\Model $tenant): bool
{
return $this->teams->contains($tenant);
}
However, on every resource I'm now getting the following error:
The model [App\Models\Category] does not have a relationship named [team]
The model [App\Models\Category] does not have a relationship named [team]
Is it possible to disable this relation behavior since we're using a morphToMany? If so, we could use the first party Tenancy setup to switch the current tenant/team in Filament, while keeping our own custom business logic in place.
11 Replies
Laurens
LaurensOP•16mo ago
Hmm diving into the Filament code, I could add a child Resource class for my resources to extend, and simply return the base query there. 🤔
public static function getEloquentQuery(): Builder
{
$query = static::getModel()::query();

if ($tenant = Filament::getTenant()) {
static::scopeEloquentQueryToTenant($query, $tenant);
}

return $query;
}
public static function getEloquentQuery(): Builder
{
$query = static::getModel()::query();

if ($tenant = Filament::getTenant()) {
static::scopeEloquentQueryToTenant($query, $tenant);
}

return $query;
}
Could work as a fix if the core Tenancy API doesn't allow this out of the box. But it'd be nice if it was possible to set the $ownershipRelationship to null or false to disable this behavior in tenancy.
Mis Tsu
Mis Tsu•16mo ago
I also facing same issue ,please let me know your solving method
Laurens
LaurensOP•16mo ago
I've now done this (on a separate branch): - Create a new Resource.php (in the root app/Filament namespace, in resources it'd get registered)
<?php

namespace App\Filament;

use Illuminate\Database\Eloquent\Builder;

class Resource extends \Filament\Resources\Resource
{
public static function getEloquentQuery(): Builder
{
return static::getModel()::query();
}
}
<?php

namespace App\Filament;

use Illuminate\Database\Eloquent\Builder;

class Resource extends \Filament\Resources\Resource
{
public static function getEloquentQuery(): Builder
{
return static::getModel()::query();
}
}
- Then let all your resources extend your custom resource class. Maybe you can publish the stubs of the default resource, so they always extend your base class when you create a new one For my app, I'm personally thinking to not use tenancy for our teams solution. Later on our custom flow will be expanded anyways that clients above teams will have multiple teams.. So then I'll probably just let Filament use that, and build a custom team switcher in the front-end
Ashk
Ashk•16mo ago
I suggest you to do that in your resource :
public static function scopeEloquentQueryToTenant(Builder $query, ?Model $tenant): Builder
{
return $query;
}
public static function scopeEloquentQueryToTenant(Builder $query, ?Model $tenant): Builder
{
return $query;
}
Laurens
LaurensOP•16mo ago
Ah yes that might be more interesting in case Filament updates to base Eloquent query 🙂
Andrew Wallo
Andrew Wallo•16mo ago
@laurensdl what problem exactly are you having? I’m using a package that is essentially Jetstream and everything is working just fine… I would also suggest updating your “getTenants()”, “canAccessTenant()”, and “getDefaultTenant()” methods to what they should be based on the HasTeams traits methods
Laurens
LaurensOP•16mo ago
I'm guessing you have tenant_id or team_ids on your models?
Andrew Wallo
Andrew Wallo•16mo ago
I will show you what I did soon Well I have company_id. Im using my package #companies but it is the same thing as Jetstream, literally the same thing except the naming is Companies instead of Teams @laurensdl use this for your User model:
class User extends Authenticatable implements FilamentUser, HasAvatar, HasTenants, HasDefaultTenant
{
use HasApiTokens;
use HasFactory;
use HasProfilePhoto;
use HasTeams;
use Notifiable;
use TwoFactorAuthenticatable;

public function canAccessPanel(Panel $panel): bool
{
return true;
}

public function getTenants(Panel $panel): array|Collection
{
return $this->allTeams();
}

public function canAccessTenant(Model $tenant): bool
{
return $this->belongsToTeam($tenant);
}

public function getDefaultTenant(Panel $panel): ?Model
{
return $this->currentTeam;
}

public function getFilamentAvatarUrl(): string
{
return $this->profile_photo_url;
}
}
class User extends Authenticatable implements FilamentUser, HasAvatar, HasTenants, HasDefaultTenant
{
use HasApiTokens;
use HasFactory;
use HasProfilePhoto;
use HasTeams;
use Notifiable;
use TwoFactorAuthenticatable;

public function canAccessPanel(Panel $panel): bool
{
return true;
}

public function getTenants(Panel $panel): array|Collection
{
return $this->allTeams();
}

public function canAccessTenant(Model $tenant): bool
{
return $this->belongsToTeam($tenant);
}

public function getDefaultTenant(Panel $panel): ?Model
{
return $this->currentTeam;
}

public function getFilamentAvatarUrl(): string
{
return $this->profile_photo_url;
}
}
This is given that you are actually using Jetstream and its default database schema...
Laurens
LaurensOP•16mo ago
Yeah, if you read my topic I'm actually not using tenant_id columns because my app works differently 🙂
In Filament v2, we already implemented the teams by applying global scopes on models which should vary per team. Relation managers then handle the associations between teams where required, else the create / edit pages ensure (new) models are synced to the correct team.
Basically, some content of our teams is shared across all teams, some is team specific I have the following: - Models which are shared across all teams - Models which are both shared across teams while also having uniques per team (so, no attachment to teams is available to everyone) - Models which are available to specific teams, but super admins can attach existing models to other teams too For that reason we're using a morphToMany inside a HasTeamAssociations trait (among other things).. which is why a tenant_id (or other naming) doesn't work out in my case. So for my conclusion is that the only possibility is to overwrite the query like Ashk mentioned. But as I mentioned, I'll likely not use the branch where I commited the tenancy implementation, as soon we'll add a clients layer above the teams, where clients will have one or multiple teams. And then Filament's Tenancy could work with actual tenant_ids there, while our custom global scopes handle our custom team logic
Andrew Wallo
Andrew Wallo•16mo ago
Okay well I guess seems like you know what you want to do then. I would honestly just suggest having a separate panel without Tenancy where the 2 tenants can see shared data... and then have one panel where their data is separate.
Laurens
LaurensOP•16mo ago
Yeah marking as solved I guess
Want results from more Discord servers?
Add your server