Laravel Nova field BelongsToMany drilling down the data - laravel

I am developing a Web Application using Laravel. I am using Laravel Nova for building the admin panel. But I am having an issue with BelongsToMany field.
I have the database schema as follow
Area
Station - has area_id because an area has many stations
manager - area_id because each user belongs to an area
Area_station - station_id and manager_id (many to many)
So in the Station nova resource, I added a field like this.
BelongsToMany::make('Managers', 'managers', Manager::class),
Therefore, when I go to the Station details page from the Nova admin panel and select "Attach manager", in the dropdown of the next page (page to attach a manager to the department), I can see all the available managers in the database.
But instead of displaying all the available managers in the drop down, I like to filter the managers/users which belongs to the same area as the selected Station. I mean when I attached a manager to the Station, I have to select a station. Is it possible to filter or to achieve what I want in Nova?

Override relatableQuery function which will determines which instances of the model may be attached to other resources, under Manager nova resource.
Approach 1
public static function relatableQuery(NovaRequest $request, $query)
{
// In case manager is get attached to another resource except Station.
if ($request->resource() == 'App\Nova\Station') {
$station = $request->findResourceOrFail();
return $query->where('area_id', $station->area_id);
}
return parent::relatableQuery($request, $query);
}
Update
Approach 2 - Learned something new just now.
You can add a relatable query for the relationship under Station Nova resource. In this case no need to check for the resource.
public static function relatableManagers(NovaRequest $request, $query)
{
$station = $request->findResourceOrFail();
return $query->where('area_id', $station->area_id);
}
Approach 2 is better in my opinion.

Related

Laravel nova and belongsToThrough?

Is there a way to display in Laravel Nova Resource a relation like this ?
$report->meter->user
where
- a report belongs to a meter
- a meter belongs to a user
I am in report ressource and I want to display related user
I struggled with this lately. What we seek is a BelongsToThrough relationship field. That type of relationship isn't natively supported by Laravel, which means there's no field for it in Nova either. However, you can easily implement it in Laravel by installing this package: https://github.com/staudenmeir/belongs-to-through
You can then add your report relationship to the User model.
// App\Models\Report
use \Znck\Eloquent\Traits\BelongsToThrough;
public function report()
{
return $this->belongsToThrough('App\Models\User','App\Models\Meter');
}
And you can use a regular BelongsTo relationship within Nova.
// App\Nova\Report
public function fields(Request $request)
{
return [
BelongsTo::make('User')
]
}
Note: This would work for the Report index/details views, but you'll want to exclude it from the edit form. (Consider a computed Text field, if you want to display the user on the edit form.)

Laravel restrict content based on customer/user/tenant

I'm in a dilemma to find some sort of logic to restrict user access to content within the same model.
For example, a supplier only can see products that they supply and the customer only can see a product which they buying. (note: Each product can have multiple supplier or customers. We call id a product node)
Now, I have the relationship set to a product belongs to many suppliers and a product belongs to multiple customers.
Currently I have the spatie roles and permissions in my site, which works great for 1 tenant (mainly our office(50-150 users)). It is not a problem if our office user can see details of multiple customers or products, but the problem starts when the customer logs in. I only want to show the product pricing or data that belonging to them. It is a big no no to see any other customer or supplier data.
I looked multi tenancy implementation, but I believe this wouldn't cover my need.
I apologise if I've overlooked something, but I try to keep the data as secured as possible.
Could you please shed some light on this dilemma and direct me to the correct path?
Many thanks for your input!
I'm assuming that all of the models you want to restrict have a relationship directly to the customer, so you can actually add a global scope that adds a default parameter to the query.
Take the following scope:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;
class CustomerOwnedScope implements Scope {
public function apply(Builder $builder, Model $model) {
if (Auth::guard('customer')->check()) {
$builder->where('customer_id', '=', Auth::guard('customer')->id);
}
}
public function extend(Builder $builder) {
$this->addWithoutCustomer($builder);
}
protected function addWithoutCustomer(Builder $builder) {
$builder->macro('withoutCustomer', function (Builder $builder) {
return $builder->withoutGlobalScope($this);
});
}
}
Any model that has this scope will automatically add the clause WHERE customer_id = ? where ? is the id of the currently authenticated customer, if one is authenticated. Assuming that you're using Laravel auth this would prevent you from having to do anything specific to achieve your goal.
It also adds the scope withoutCustomer() that would prevent the where clause from being added.
The simplest way to add this to a model that belongs to a customer would be to create yourself a trait (concern) like so:
<?php
namespace App\Concerns;
use App\Scopes\CustomerOwnedScope;
trait OwnedByCustomer {
public static function bootOwnedByCustomer() {
static::addGlobalScope(new CustomerOwnedScope);
}
public function customer() {
$this->belongsTo(Customer::class, 'customer_id');
}
}
This would add the customer relationship as well as add the scope to automatically query based on the current customer.
You can obviously modify this further to include other relationships, or you can add some more conditions to only apply for customers with a certain flag set, or not set (for internal users, etc).
This whole approach does assume that your internal admin users and your external customer users are using different auth guards (which would be the ideal approach in this situation).
I should add that the code above is taken from an article I wrote on the subject of multi-tenancy, specifically the part about dealing with tenants in a single database. If you would like, you can read it here: https://ollieread.com/articles/laravel-multi-tenancy-avoiding-over-engineering#single-database

Laravel Nova Override BelongsTo Language field based on already translated resources

Imagine the following 3 models with fields:
Listing:
id
ListingTranslation:
id
listing_id
language_id
title
Language:
name
iso
Inside my ListingTranslation create/update form how can I filter the language selector to NOT SHOW the languages that have been already translated?
(i.e. If I have 2 languages ES (id 1) and EN (id 2) and if I have a listing with id 1 and this listing already has a listing_translation with id 1, listing_id 1 and language_id 1, the language selector should only show EN as an option).
Language selector:
BelongsTo::make('Language')
Laravel Nova documentation provides the following method to filter the queries that are used to populate relationship model selection menus:
public static function relatableQuery(NovaRequest $request, $query)
{
return $query->where('user_id', $request->user()->id);
}
However, I do not know how to access something like listing_id from this method.
Instead of creating Nova resource for pivot table ListingTranslation, you can make use of BelongsToMany relation.
Under Listing Nova resource fields add BelongsToMany::make('Languages'). Assuming you have already defined the relationship in Listing model.
At this point when you attach a language which is already attached, you will see an error message The language is already attached
But if you still want to stop listing the already attached languages you can add relatableQuery given below under Listing nova resource.
public static function relatableLanguages(NovaRequest $request, $query)
{
$listing = $request->findResourceOrFail();
return $query->whereNotIn('id', $listing->languages->pluck('id'));
}
Hope this will help you.

Problem in showing additional field of pivot table in Larvel Nova

I am using Laravel Nova for my admin side.
I have this problem that in my pivot table, I added additional field.
I have user table and event table. User can have many event, and event can have many users. So I created a pivot table for the 2 models. But I want to the status of the event of user. So I added additional field for status in pivot table.
My problem now is in the Laravel Nova.
In my event resource, I have this:
BelongsToMany::make('Pending Users', 'pendingUsers', 'App\Nova\User'),
It shows another table for pending clients. I can already get the list of users in specific event but I can't show to the table the status of the event of user. If the user is approved or still pending.
What strategy is best in my case? I am thinking to make a model for Event and User.
I don't know how to show the additional field of pivot table in BelongsToMany in Laravel Nova. It just shows what I added in User Resource of Nova. Like ID and name. But I don't know where the status of user's event will show. I want to show it in the table of BelongsToMany of events to users.
You need to make sure that you're defining the relationship in BOTH models, and then letting Nova know which fields you want to show like this:
In your event resource:
BelongsToMany::make('Users')
->fields(function() {
return [
Text::make('status')
];
}),
In your event model:
public function users()
{
return $this->belongsToMany(User::class)->withPivot('status');
}
In your user model:
public function events()
{
return $this->belongsToMany(Event::class)->withPivot('status');
}
I got this working by defining ->withPivot('filed name)' on the relationship it drove me insane for a long time.
BelongsToMany::make('your_relationship')
->fields(function () {
return [
Text::make('your_text'),
];
})
Your relation will be here something just like this what i said previous
public function users()
{
return $this->belongsToMany(User::class)->withPivot('filed name');
}

Laravel nova overriding/setting custom value for the Fields or Has Through relationship

I am developing a Web application using Laravel. For the admin panel, I am using Laravel Nova. What I am trying to do now is that I need to use data from the table which has relationship through another table. To be, clear, see my database structure below.
items
=====
id
name
price
sub_category_id
sub_categories
==============
id
name
parent_category_id
parent_categories
=================
id
name
What I am trying to achieve inside the Nova is that I want to display the parent category name of the item on the item index/list page. The first thing is that I do not want to create custom attribute something like this in the model
protected $appends = [
'parent_category_name'
];
function getParentCategoryNameAttribute()
{
//code here
}
Therefore, there are two solutions I can think of. The first solution is using the HasThrough relationship. But I cannot find it in Nova. So, I cannot use it. The second solution is that overriding the field value on render. Something like this.
Text::make("fieldname")->fillUsing(function($request, $model, $attribute, $requestAttribute) {
//$model->sub_category->parent_category - then I can return a value
return "Parent category name";
})->onlyOnIndex()
But the above code is not working. So, what would be the best approach to handle the has-through relationship in Nova?
Assuming you have defined the relationship sub_category & parent_category properly.
Define the relationship in Item model as below
public function parent_category()
{
return $this->sub_category->parent_category();
}
Then use it in Item resource as below.
BelongsTo::make('Parent Category')
->hideWhenCreating()
->hideWhenUpdating(),

Resources