Create Admin Panel with Laravel Backpack Part 3

In today article “Create Admin Panel with Laravel Backpack Part 3” we will talk about how to create a quick and powerful admin panel. For this article we gonna update our models, controllers and requests to be able to perform CRUD.

In the first part we talk about how to setup basic installation and configuration of Laravel Backpack. In the second part we setup the database and create models, controller. We also created routes and menu. Now we will create the basic crud for a book database.

If you didn’t follow the previous article be sure you got them :

Setup Models and relationship

The second part we setup database and create models and controllers even routes but if you tried to save a model you will have an error. You get a mass assignment error and we gonna fix that right now.

If you wish to know why you get this error take a look at the laravel docs. The simple explanation is laravel is protecting you from user input in case of the user send you bad request or edit a request.

Authors

Let’s begin with our first model authors and let’s make it look like this.

class Author extends Model
{
    use CrudTrait;
 
    /*
    |--------------------------------------------------------------------------
    | GLOBAL VARIABLES
    |--------------------------------------------------------------------------
    */
 
    protected $table = 'authors';
    // protected $primaryKey = 'id';
    // public $timestamps = false;
    // protected $guarded = ['id'];
    protected $fillable = ['title','slug','biography'];
    // protected $hidden = [];
    // protected $dates = [];
 
    /*
    |--------------------------------------------------------------------------
    | FUNCTIONS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | RELATIONS
    |--------------------------------------------------------------------------
    */
    public function books(){
        return $this->hasMany('App\Models\Book');
    }
    /*
    |--------------------------------------------------------------------------
    | SCOPES
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | ACCESORS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | MUTATORS
    |--------------------------------------------------------------------------
    */
}

Basically here we just add the field we want to be mass-assignable in the field array and we define the relationship with books. To be concise and write clean code it’s recommended to put all you relationship closely, same things with scopes, accesors and mutators.

At this stage we are able to perform basic crud operations but what if we need to make some validation. For example if we want the title no longer than 100 characters and we want it required.

We will do that in the request file create with the models. The request file is located in app/Http/Request/ModelRequest.php so for the authors model the request file is app/Http/Request/AuthorRequest.php . Let’s edit it and add some rules and message.

class AuthorRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // only allow updates if the user is logged in
        return backpack_auth()->check();
    }
 
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|min:2|max:100',
            'slug' => 'required'
        ];
    }
 
    /**
     * Get the validation attributes that apply to the request.
     *
     * @return array
     */
    public function attributes()
    {
        return [
            //
        ];
    }
 
    /**
     * Get the validation messages that apply to the request.
     *
     * @return array
     */
    public function messages()
    {
        return [
            //
        ];
    }
}

So it’s the basic laravel validation, the functions rules is where you defined your validation rules. The function messages() is where you define the message to show when the rules is not valid.

This Request is use both for create and update. If you need to setup different rules for update you have to duplicate this request and change the rules. You need to change the request class in the controller too. We will experiment that later, just know it’s simple and possible.

Add author

And the list is ready too you can make a search and we will customize those later.

Authors list

Books

So the others models is basically the same process. First you make fix the fillable array and make relationship.

app/Models/Book.php

class Book extends Model
{
    use CrudTrait;
 
    /*
    |--------------------------------------------------------------------------
    | GLOBAL VARIABLES
    |--------------------------------------------------------------------------
    */
 
    protected $table = 'books';
    protected $primaryKey = 'id';
    // public $timestamps = false;
    // protected $guarded = ['id'];
    protected $fillable = ['title','author','author_id','author_bio', 'title_slug','isbn13',
    'isbn10','price','format','publisher','pubdate','edition','lexile','subjects','pages',
        'dimensions','overview','synopsis','excerpt','toc','editorial_reviews'];
    // protected $hidden = [];
     protected $dates = ['pubdate'];
 
    /*
    |--------------------------------------------------------------------------
    | FUNCTIONS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | RELATIONS
    |--------------------------------------------------------------------------
    */
    public function author(){
        return $this->belongsTo('App\Models\Author');
    }
 
    public function categories(){
        return $this->belongsToMany('App\Models\Category','book_category','book_id','category_id');
    }
    public function subcategories(){
        return $this->belongsToMany('App\Models\Subcategory','book_category','book_id','subcategory_id');
    }
 
    /*
    |--------------------------------------------------------------------------
    | SCOPES
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | ACCESORS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | MUTATORS
    |--------------------------------------------------------------------------
    */
}

app/Http/Request/BookRequest.php

class BookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // only allow updates if the user is logged in
        return backpack_auth()->check();
    }
 
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|min:2|max:255',
            'author' => 'required',
            'author_id' => 'required',
            'title_slug' => 'required',
            'isbn13' => 'required|min:13|max:13',
            'isbn10' => 'required|min:10|max:10',
            'format' => 'required',
            'publisher' => 'required',
            'pages' => 'required|integer'
        ];
    }
 
    /**
     * Get the validation attributes that apply to the request.
     *
     * @return array
     */
    public function attributes()
    {
        return [
            //
        ];
    }
 
    /**
     * Get the validation messages that apply to the request.
     *
     * @return array
     */
    public function messages()
    {
        return [
            //
        ];
    }
}

A validation example in BookRequest.php is for isbn13. We know that we only need 13 characters so we set it required and min, max 13.

Subject

app/Models/Subject.php

class Subject extends Model
{
    use CrudTrait;
 
    /*
    |--------------------------------------------------------------------------
    | GLOBAL VARIABLES
    |--------------------------------------------------------------------------
    */
 
    protected $table = 'subjects';
    protected $primaryKey = 'id';
    // public $timestamps = false;
    // protected $guarded = ['id'];
    protected $fillable = ['title','slug'];
    // protected $hidden = [];
    // protected $dates = [];
 
    /*
    |--------------------------------------------------------------------------
    | FUNCTIONS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | RELATIONS
    |--------------------------------------------------------------------------
    */
    public function categories(){
        return $this->hasMany('App\Models\Category');
    }
    /*
    |--------------------------------------------------------------------------
    | SCOPES
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | ACCESORS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | MUTATORS
    |--------------------------------------------------------------------------
    */
}

app/Http/Request/SubjectRequest.php

class SubjectRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // only allow updates if the user is logged in
        return backpack_auth()->check();
    }
 
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
             'title' => 'required|min:2|max:255',
            'slug' => 'required'
        ];
    }
 
    /**
     * Get the validation attributes that apply to the request.
     *
     * @return array
     */
    public function attributes()
    {
        return [
            //
        ];
    }
 
    /**
     * Get the validation messages that apply to the request.
     *
     * @return array
     */
    public function messages()
    {
        return [
            //
        ];
    }
}

Category

app/Models/Category.php

class Category extends Model
{
    use CrudTrait;
 
    /*
    |--------------------------------------------------------------------------
    | GLOBAL VARIABLES
    |--------------------------------------------------------------------------
    */
 
    protected $table = 'categories';
    protected $primaryKey = 'id';
    // public $timestamps = false;
    // protected $guarded = ['id'];
    protected $fillable = ['title','subject_id','slug','count'];
    // protected $hidden = [];
    // protected $dates = [];
 
    /*
    |--------------------------------------------------------------------------
    | FUNCTIONS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | RELATIONS
    |--------------------------------------------------------------------------
    */
    public function subject(){
        return $this->belongsTo('App\Models\Subject');
    }
    public function subcategories(){
        return $this->hasMany('App\Models\Subcategory');
    }
 
    /*
    |--------------------------------------------------------------------------
    | SCOPES
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | ACCESORS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | MUTATORS
    |--------------------------------------------------------------------------
    */
}

app/Http/Requests/CategoryRequest.php

class CategoryRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // only allow updates if the user is logged in
        return backpack_auth()->check();
    }
 
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|min:2|max:255',
            'subject_id' => 'required',
            'slug' => 'required'
        ];
    }
 
    /**
     * Get the validation attributes that apply to the request.
     *
     * @return array
     */
    public function attributes()
    {
        return [
            //
        ];
    }
 
    /**
     * Get the validation messages that apply to the request.
     *
     * @return array
     */
    public function messages()
    {
        return [
            //
        ];
    }
}
Categories list laravel guy
Categories list

Subcategory

app/Models/Subcategory.php

class Subcategory extends Model
{
    use CrudTrait;
 
    /*
    |--------------------------------------------------------------------------
    | GLOBAL VARIABLES
    |--------------------------------------------------------------------------
    */
 
    protected $table = 'subcategories';
    // protected $primaryKey = 'id';
    // public $timestamps = false;
    // protected $guarded = ['id'];
    protected $fillable = ['title', 'category_id','slug','count'];
    // protected $hidden = [];
    // protected $dates = [];
 
    /*
    |--------------------------------------------------------------------------
    | FUNCTIONS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | RELATIONS
    |--------------------------------------------------------------------------
    */
    public function category(){
        return $this->belongsTo('App\Models\Category');
    }
    /*
    |--------------------------------------------------------------------------
    | SCOPES
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | ACCESORS
    |--------------------------------------------------------------------------
    */
 
    /*
    |--------------------------------------------------------------------------
    | MUTATORS
    |--------------------------------------------------------------------------
    */
}

app/Http/Requests/SubcategoryRequest.php

class SubcategoryRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // only allow updates if the user is logged in
        return backpack_auth()->check();
    }
 
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|min:2|max:255',
            'category_id' => 'required',
            'slug' => 'required'
        ];
    }
 
    /**
     * Get the validation attributes that apply to the request.
     *
     * @return array
     */
    public function attributes()
    {
        return [
            //
        ];
    }
 
    /**
     * Get the validation messages that apply to the request.
     *
     * @return array
     */
    public function messages()
    {
        return [
            //
        ];
    }
}

Setup Controllers

After editing the models and request our form still not friendly. We would like to edit the column name or adding some calculated columns. To do all of those things we need to edit the controllers. Let’s take an example the model Category it’s related to Subcategory and Subject.

Category add

We would like to have a list of subject then choose one. Let’s see how we can do that.

class CategoryCrudController extends CrudController
{
    public function setup()
    {
        /*
        |--------------------------------------------------------------------------
        | CrudPanel Basic Information
        |--------------------------------------------------------------------------
        */
        $this->crud->setModel('App\Models\Category');
        $this->crud->setRoute(config('backpack.base.route_prefix') . '/category');
        $this->crud->setEntityNameStrings('category', 'categories');
 
        /*
        |--------------------------------------------------------------------------
        | CrudPanel Configuration
        |--------------------------------------------------------------------------
        */
 
        $this->crud->setColumns(['title', 'subject_id','slug', 'count']);
 
        $this->crud->setColumnDetails('subject_id',[
           'name' => 'subject_id',
            'type' => 'select',
            'label' => 'Subject',
            'entity' => 'subject',
            'attribute' => 'title',
            'model' => 'App\Models\Subject',
        ]);
 
        $this->crud->addField([
            'name' => 'title',
            'type' => 'text',
            'label' => "Title"
        ]);
 
        $this->crud->addField([
           'name' => 'subject_id',
            'type' => 'select2',
            'label' => 'Subject',
            'entity' => 'subject',
            'attribute' => 'title',
            'model' => 'App\Models\Subject',
            'options' => (function ($query) {
                return $query->orderBy('title', 'ASC')->get();
            })
        ]);
 
        $this->crud->addField([
            'name' => 'slug',
            'type' => 'text',
            'label' => "Slug"
        ]);
        $this->crud->addField([
           'name' => 'count',
            'type' => 'number',
            'label' => 'Number of entity'
        ]);
 
        // add asterisk for fields that are required in CategoryRequest
        $this->crud->setRequiredFields(StoreRequest::class, 'create');
        $this->crud->setRequiredFields(UpdateRequest::class, 'edit');
    }
 
    public function store(StoreRequest $request)
    {
        // your additional operations before save here
        $redirect_location = parent::storeCrud($request);
        // your additional operations after save here
        // use $this->data['entry'] or $this->crud->entry
        return $redirect_location;
    }
 
    public function update(UpdateRequest $request)
    {
        // your additional operations before save here
        $redirect_location = parent::updateCrud($request);
        // your additional operations after save here
        // use $this->data['entry'] or $this->crud->entry
        return $redirect_location;
    }
}

Let’s explain each part to know what is the concern. The first things we change in the setup() function we remove the instruction $this-crud->setFromDb(). We replace it with this line :

 $this->crud->setColumns(['title', 'subject_id','slug', 'count']);

This line tell our controller to use that column in that order to display our list. We can change the order by just changing the place of column in the array. There’s more complex way to order the column we will meet them later.

The second things we do is customize the column named subject_id with this code:

$this->crud->setColumnDetails('subject_id',[
           'name' => 'subject_id',
            'type' => 'select',
            'label' => 'Subject',
            'entity' => 'subject',
            'attribute' => 'title',
            'model' => 'App\Models\Subject',
        ]);

In this line of code we tell the subject_id how it should display. The entity attribute is the name of the relationship. The attribute is the value you actually want to show and the model is the model class. You can find the list of column type in the docs.

List of category with custom column name
List with custom column name

The third and last things is we customize the form field. We add a field for each column but let take a look at the most complex.

  $this->crud->addField([
           'name' => 'subject_id',
            'type' => 'select2',
            'label' => 'Subject',
            'entity' => 'subject',
            'attribute' => 'title',
            'model' => 'App\Models\Subject',
            'options' => (function ($query) {
                return $query->orderBy('title', 'ASC')->get();
            })
        ]);

It’s basically the same as the column details the difference is the type. The type is select2 there’s a bunch of field type. You can find them in the docs. The last attribute options tell how to show the list. In this example we make it order the list by name.

Add form with list of choice
Add Category with relation

You can find the code of this project from our GitHub repository here Link

Conclusion

Here is the end of the article “Create Admin Panel with Laravel Backpack Part 3” witch is the third part of this series. In this article we customize the models and make validations. We update our controllers to edit column name and add custom field. As usual if you get any trouble just leave a comment.

You may also like...

1 Response

  1. August 14, 2019

    […] (Setup model and relationship )Create admin panel with laravel backpack part 3 […]

Leave a Reply

Your email address will not be published. Required fields are marked *