#java #spring #thymeleaf #modelattribute
#java #spring #thymeleaf #атрибут модели
Вопрос:
У меня есть форма для редактирования пользователя, у пользователя есть роли, которые представляют собой список объектов типа Authority, я хочу иметь возможность использовать флажки (необязательно) для установки ролей, которые будут у пользователя, но я понятия не имею, как реализовать форму в thymeleaf и какпередайте пользовательский объект с заданными ролями контроллеру.
Это мой пользователь
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name="username", nullable = false, unique = true)
@NotBlank @Size(min=5, message = "Не менeе 5 знаков")
private String username;
@NotBlank @Size(min=5, message = "Не менeе 5 знаков")
@Column(name = "password")
private String password;
@Column(name = "enabled")
private boolean enabled;
@Column(name = "name")
private String name;
@Column(name = "surname")
private String surname;
@Column(name = "email", nullable = false, unique = true)
private String email;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
}, fetch = FetchType.EAGER)
@JoinTable(
name = "users_authorities",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "authority_id")
)
private Set<Authority> authorities = new HashSet<>();
public User(String username, String password, boolean enabled,
String name, String surname, String email) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.name = name;
this.surname = surname;
this.email = email;
}
public void addAuthority(Authority authority) {
this.authorities.add(authority);
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
}
Это мой редактор пользовательских форм редактирования
@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findById(id));
model.addAttribute("allAuthorities", authorityService.findAll());
return "users/edit-user";
}
Это мое представление формы редактирования пользователя
<body>
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<input type="hidden" th:field="*{id}" id="id">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth:${allAuthorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="checkbox" name="authorities" th:checked="${user.authorities.contains(auth)}">
</label>
</div>
<input type="submit" value="Edit">
</form>
</body>
Он помещает contoller, он получает данные из моей формы
@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
@ModelAttribute("user") User user,
@RequestParam("authorities") List<Authority> authorities) {
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
И это мой класс полномочий, если вам нужно
@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
@Id
private Long id;
@Column(name = "authority")
private String authority;
@Transient
@ManyToMany(mappedBy = "authorities")
private Set<User> users;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
Я пытаюсь передать список ролей отдельно от пользовательского объекта, но это также не работает и выдает ошибку неправильного запроса.
Комментарии:
1. Не могли бы вы добавить какой-нибудь код, который вы пробовали, или что-то эквивалентное?
Ответ №1:
Чтобы решить проблему, я добавил новое проверенное поле в Entity authority
@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
@Id
private Long id;
@Column(name = "authority", nullable = false, unique = true)
private String authority;
@Transient
private boolean checked;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
Перед отправкой представления в него я изменил поле authorites пользовательского объекта.
@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {
User user = userService.findById(id);
List<Authority> allAuthorities = authorityService.findAll();
for(Authority auth : allAuthorities) {
if(user.getAuthorities().contains(auth)) {
auth.setChecked(true);
}
}
user.setAuthorities(allAuthorities);
model.addAttribute("user", user);
return "users/edit-user";
}
Я также изменил шаблон thymeleaf
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth, itemStat: ${user.authorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].id}">
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].authority}">
<input type="checkbox" th:checked="${auth.checked}"
th:field="*{authorities[__${itemStat.index}__].checked}">
</label>
</div>
<input type="submit" value="Edit">
</form>
Мой контроллер put выглядит следующим образом
@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
@ModelAttribute("user") User user) {
List<Authority> userAuthorities = user.getAuthorities();
userAuthorities.removeIf(auth -> !auth.isChecked());
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
Мои методы сохранения и удаления из UserService выглядят следующим образом (если это будет полезно)
@Override
@Transactional
public User save(User user) {
Optional<User> userFromDB = userRepository.findByUsername(user.getUsername());
Optional<Authority> userRole;
if(userFromDB.isPresent()) {
return null;
}
userRole = authorityRepository.findByAuthority("ROLE_USER");
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRole.ifPresent(user::addAuthority);
return userRepository.save(user);
}
@Override
@Transactional
public User update(User user) {
Optional<User> userFromDB = userRepository.findById(user.getId());
if(userFromDB.isPresent()){
//Adding the password for successfully update user in DB
user.setPassword(userFromDB.get().getPassword());
return userRepository.save(user);
}
return null;
}
Возможно, это не самое элегантное решение, но я еще не нашел другого