Play CMS is shipped with DefaultParty as a default implementation of party, DefaultPartyRole as a default implementation of role, DefaultPartyManage, DefaultRoleManager  and default user interface for their management.

Each of the elements can be extended and customized in main project. 

Customizing Party class

The Party objects are managed by the PartyService which can be overridden to support customized Party implementation. The overridden PartyService can be enabled by providing a custom Dependency Injection Module binding (see play.api.inject.Module#Module) or by defining a custom play.inject.guice.GuiceApplicationLoader with overridden bindings.

Example:

// CustomPartyService.java
public class CustomPartyManager extends ch.insign.playauth.party.PartyService<User> {

    @Override
    public Class<User> getPartyClass() {
        return User.class;
    }
}
// CustomApplicationLoader.java
public class CustomApplicationLoader extends GuiceApplicationLoader {

    @Override
    public GuiceApplicationBuilder builder(Context context) {
        return super.builder(context)
                .overrides(
                        bind(PartyService.class).to(CustomPartyService.class));
    }
}
# Take control of how the application is initialized
play.application.loader = "CustomApplicationLoader"

Customizing Party list

A list of users is rendered by datatable. You can find a detailed guide about play-cms integration with datatable in the Datatable integration section. The user searching functionality is already implemented in PartyDatatableService.
Let's look on example where new fields are added to the list:

// imports ommited

public class UserDatatableService extends PartyDatatableService<User> {

    @Inject
    public UserDatatableService(CMSApi cmsApi, PlayAuthApi playAuthApi, JPAApi jpaApi) {
        super(cmsApi, playAuthApi, jpaApi);
    }

    @Override
    protected Class<User> getEntityClass() {
        return User.class;
    }

    @Override
    protected List<String> getSortableFields() {
        return Arrays.asList("name", "email", "roleName", "lastLogin", "loginCount", "");
    }

    @Override
    protected List<Function<User, String>> getTableFields() {
        return Arrays.asList(
                getPartyName(),
                getPartyEmail(),
                getPartyRoles(),
                getUserLastLogin(),
                getUserLoginCount(),
                getPartyActions()
        );
    }

    private Function<User, String> getUserLastLogin() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy HH:mm:ss");

        return user -> Optional.ofNullable(user.getLastLogin()).map(dateFormat::format).orElse("-");
    }

    private Function<User, String> getUserLoginCount() {
        return user -> String.valueOf(user.getLoginCount());
    }
}

PartyDatatableService already provides number of helper methods that helps to obtain various fields from party like name, email, roles or available actions. UserDatatableService extends list of such helpers by adding new methods to receive last login date and login counter. It also overrides getSortableFields method in order to bring ability to sort a table by newly added fields. You may note that last column in list of sortable columns doesn't have name. This is because last column in getTableFields was set to getPartyActions that doesn't have any representation in db and couldn't be reordered. Yet, in the datatable it is required that the number of sortable columns should match the actual number of columns.

Once service is done, template should be updated accordingly:

@()

@import ch.insign.cms.views.html.admin.shared.layout
@import ch.insign.cms.models.CMS

@pageFooter = {
    <script src="@{ch.insign.cms.views.html.tags._asset("backend/js/user_list.js")}" type="text/javascript"></script>
}

@layout(new ch.insign.cms.views.admin.utils.AdminContext, "", "", null, pageFooter) {
    <div class="modal fade" id="deletePartyModal"></div>

    <input type="hidden" id="datatableUrl" value="@ch.insign.cms.controllers.routes.PartyController.datatable()" name="datatableUrl"/>
    <div class="row">
        <div class="col-md-12">
            <div class="portlet">
                <div class="portlet-title">
                    <div class="caption">
                        <i class="fa fa-user"></i> @Messages("backend.user.customer.title")
                    </div>

                    <div class="actions">
                        <a href="@CMS.getRouteResolver.createParty()" class="btn red">
                            <i class="fa fa-plus"></i> @Messages("backend.newEntry.button")
                        </a>
                    </div>
                </div>

                <div class="portlet-body">
                    <table class="table table-striped table-bordered table-hover" id="user_list">
                        <thead>
                            <tr>
                                <th class="col-xs-3">@Messages("backend.user.name")</th>
                                <th class="col-xs-3">@Messages("backend.user.email")</th>
                                <th class="col-xs-2">@Messages("backend.user.roles")</th>
                                <th class="col-xs-2">@Messages("backend.user.lastLogin")</th>
                                <th class="col-xs-1">@Messages("backend.user.loginCount")</th>
                                <th class="col-xs-1">@Messages("backend.user.actions")</th>
                            </tr>
                        </thead>
                        <tbody></tbody>
                    </table>
                </div>
            </div>
        </div>
}

There are just two additional table headers in this template if compared to the default implementation. The additional headers are added to match the number of columns received from the server, because otherwise the datatable will generate an exception.

This template should be injected into a project by implementing PartyListView .

Customizing Party's edit and create forms

Create class which extends PartyFormManager and implement it's methods:

  • editForm:  returns the rendered edit form for the custom party
  • createForm: returns the rendered create form for the custom party

Register new class in registerPartyFormManager() method of a setup class

Example:

public class DefaultPartyFormManager implements PartyFormManager {

    @Override
    public Html editForm(Form editForm, Party party) {
        return editForm.render(new AdminContext(), editForm, (User) party);
    }

    @Override
    public Html createForm(Form form) {
        return createForm.render(form);
    }
}

 

Note: create and edit templates can use default party's templates as a base template and only extend it with new fields.

Example:

@ch.insign.cms.views.html.admin.party.editForm(a, userForm, user) {
    <div class="form-group">
        @inputText(
            userForm("lastName"),
            'class -> "form-control",
            'placeholder -> Messages.get("backend.user.lastName"),
            '_label -> Messages.get("backend.user.lastName")
        )
    </div>
    <div class="form-group">
        @inputText(
            userForm("phone"),
            'class -> "form-control",
            'placeholder -> Messages.get("backend.user.phone"),
            '_label -> Messages.get("backend.user.phone")
        )
    </div>
}

 

Party validation

play-cms provides build-in validation constrains for various typical actions. :

  • ValidateRepeatedPassword - checks password and password verification entered by user matches.
  • ValidateExisitngEmail - checks that the email entered by user doesnt exists in DB or linked to Party that was edited.
  • ValidateNewEmail - checks that email doesn't present in DB.

All constraints listed above are enabled for default Party implementation.

At different lifecycle events party could be validated with own set of constraint. To add own constraint on one of the events we provide predefined constraint groups:

  • OnCreate - triggers constrains on user creation
  • OnPasswordChange - triggers constrains on password change
  • OnUpdate - triggers constrains on party update
class User extends DefaultParty {

    ....

    // Phone field wouldn't be mandatory on registration, but would be required on profile update
    @Constraints.Required(groups = PartyConstrains.OnUpdate.class)
    private String phone;

    ....
}

Note, that constraints without groups defiend would be always triggered on create and on update events.

Customizing Party's events

Create class which extends PartyEvents and implement it's methods:

  • onCreate: triggers when party is created. All custom party fields should be copied from form class to party class in this method, otherwise they wouldn't be saved.
  • onUpdate: triggers when party is updated.
  • onDelete: triggers when party is deleted.

Register new class in registerPartyEventHandler() method of a setup class.

Example:

public class DefaultPartyHandler implements PartyEvents {

   @Override
    public void onCreate(Form form, Party party) {
        User user = (User) party;
        User userForm = ((Form<User>) form).get();
        user.setLastName(userForm.getLastName());
        user.setPhone(userForm.getPhone());
    }
 
    @Override
    public void onUpdate(Form form, Party party) {

    }
 
    @Override
    public void onDelete(Party party) {

    }
}

 

 

Customizing Party's controller actions

Customizing party's controllers actions is not really necessary but can be easily done by creating new class, that extends from RouteResolver.

Register new class in registerResolver() method of a setup class and implement it's methods.

Each of it's method should return the call to appropriate action of your own Party Controller.

Example:

public class DefaultRouteResolver implements RouteResolver {
    @Override
    public Call login() {
        return routes.AuthController.login();
    };
    @Override
    public Call logout(String backURL) {
        return routes.AuthController.logout(backURL);
    }
    @Override
    public Call listParties() {
        return routes.PartyController.list();
    }
    @Override
    public Call editParty(String id) {
        return routes.PartyController.editParty(id);
    } 
    //...
}

 

Note: Default RouteResolver is already implemented in play-cms module.