фигня

This commit is contained in:
Kirill 2024-06-13 16:22:54 +04:00
parent f50af7950d
commit c381c41ae1
19 changed files with 492 additions and 79 deletions

View File

@ -76,4 +76,25 @@ public class FileHelper {
throw new InternalError(e);
}
}
public static String saveToFilmFile(Path path, long id, String extension, byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
try {
final Path dir = Path.of(
path.toFile().getAbsolutePath(),
"resources/image");
if (!Files.exists(dir)) {
Files.createDirectories(dir);
}
final Path temp = Path.of(
dir.toFile().getAbsolutePath(),
String.format("film-%s.%s", id, extension));
Files.write(temp, bytes);
return temp.toFile().getAbsolutePath();
} catch (IOException e) {
throw new InternalError(e);
}
}
}

View File

@ -1,5 +1,8 @@
package com.example.demo.films.api;
import java.nio.file.Path;
import java.util.Set;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -10,6 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.core.file.FileHelper;
import com.example.demo.core.configuration.Constants;
import com.example.demo.films.model.FilmEntity;
import com.example.demo.films.service.FilmService;
@ -18,6 +22,7 @@ import com.example.demo.genres.model.GenreEntity;
import com.example.demo.genres.service.GenreService;
import jakarta.validation.Valid;
import jakarta.servlet.ServletContext;
@Controller
@RequestMapping(FilmController.URL)
@ -27,18 +32,32 @@ public class FilmController {
private static final String FILM_EDIT_VIEW = "film-edit";
private static final String FILM_ATTRIBUTE = "film";
private final Set<String> allow = Set.of("jpg", "jpeg", "png", "svg");
private final GenreService genreService;
private final FilmService filmService;
private final ModelMapper modelMapper;
private final Path servletContextPath;
public FilmController(GenreService genreService, FilmService filmService, ModelMapper modelMapper) {
public FilmController(GenreService genreService, FilmService filmService, ModelMapper modelMapper,
ServletContext servletContext) {
this.genreService = genreService;
this.filmService = filmService;
this.modelMapper = modelMapper;
this.servletContextPath = Path.of(servletContext.getRealPath("/"));
}
private FilmDto toDto(FilmEntity entity) {
return modelMapper.map(entity, FilmDto.class);
final FilmDto dto = modelMapper.map(entity, FilmDto.class);
if (entity.getImage() != null && entity.getImage().length > 0) {
final Path contextPath = servletContextPath;
FileHelper.saveToFilmFile(
contextPath,
entity.getId(),
entity.getImageExtension(),
entity.getImage());
}
return dto;
}
private GenreDto toGenreDto(GenreEntity entity) {
@ -46,7 +65,20 @@ public class FilmController {
}
private FilmEntity toEntity(FilmDto dto) {
return modelMapper.map(dto, FilmEntity.class);
Long genreId = dto.getGenreId();
GenreEntity genre = genreService.get(genreId);
final String extension = FileHelper.getExtension(dto.getImage());
if (!allow.contains(extension.toLowerCase())) {
throw new IllegalArgumentException(
String.format("Only %s files", String.join(", ", allow)));
}
FilmEntity entity = modelMapper.map(dto, FilmEntity.class);
entity.setGenre(genre);
entity.setImageExtension(extension);
entity.setImage(FileHelper.getFileBytes(dto.getTmpPath()));
return entity;
}
@GetMapping
@ -78,6 +110,13 @@ public class FilmController {
if (bindingResult.hasErrors()) {
return FILM_EDIT_VIEW;
}
if (film.getImage().isEmpty()) {
bindingResult.rejectValue("image", "films:image", "Изображение не выбрано.");
model.addAttribute(FILM_ATTRIBUTE, film);
return FILM_VIEW;
}
film.setGenreName(genreService.get(film.getGenreId()).getName());
film.setTmpPath(FileHelper.toTmpPath(film.getImage(), servletContextPath));
filmService.create(toEntity(film));
return Constants.REDIRECT_VIEW + URL;
}
@ -90,6 +129,11 @@ public class FilmController {
throw new IllegalArgumentException();
}
model.addAttribute(FILM_ATTRIBUTE, toDto(filmService.get(id)));
model.addAttribute(
"genres",
genreService.getAll().stream()
.map(this::toGenreDto)
.toList());
return FILM_EDIT_VIEW;
}
@ -105,6 +149,13 @@ public class FilmController {
if (id <= 0) {
throw new IllegalArgumentException();
}
if (film.getImage().isEmpty()) {
bindingResult.rejectValue("image", "films:image", "Изображение не выбрано.");
model.addAttribute(FILM_ATTRIBUTE, film);
return FILM_VIEW;
}
film.setGenreName(genreService.get(film.getGenreId()).getName());
film.setTmpPath(FileHelper.toTmpPath(film.getImage(), servletContextPath));
filmService.update(id, toEntity(film));
return Constants.REDIRECT_VIEW + URL;
}

View File

@ -5,15 +5,22 @@ import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
public class FilmDto {
private Long id;
@NotNull
private Long genreId;
@NotNull
@Min(1)
private String genreName;
@NotBlank
@Size(min = 5, max = 50)
private String name;
private String imageExtension;
private MultipartFile image;
private Path tmpPath;
public Long getId() {
return id;
@ -31,6 +38,14 @@ public class FilmDto {
this.genreName = genreName;
}
public Long getGenreId() {
return genreId;
}
public void setGenreId(Long genreId) {
this.genreId = genreId;
}
public String getName() {
return name;
}
@ -39,4 +54,28 @@ public class FilmDto {
this.name = name;
}
public MultipartFile getImage() {
return image;
}
public void setImage(MultipartFile image) {
this.image = image;
}
public Path getTmpPath() {
return tmpPath;
}
public void setTmpPath(Path tmpPath) {
this.tmpPath = tmpPath;
}
public String getImageExtension() {
return imageExtension;
}
public void setImageExtension(String imageExtension) {
this.imageExtension = imageExtension;
}
}

View File

@ -1,7 +1,10 @@
package com.example.demo.films.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
@ -10,4 +13,10 @@ import com.example.demo.films.model.FilmEntity;
public interface FilmRepository
extends CrudRepository<FilmEntity, Long>, PagingAndSortingRepository<FilmEntity, Long> {
Optional<FilmEntity> findByNameIgnoreCase(String name);
Page<FilmEntity> findBy(Pageable pageable);
List<FilmEntity> findByGenreId(long genreId);
Page<FilmEntity> findByGenreId(long genreId, Pageable pageable);
}

View File

@ -5,6 +5,9 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -34,6 +37,16 @@ public class FilmService {
return StreamSupport.stream(repository.findAll(Sort.by("id")).spliterator(), false).toList();
}
@Transactional(readOnly = true)
public Page<FilmEntity> getAll(long genreId, int page, int size) {
final Pageable pageable = PageRequest.of(page, size, Sort.by("id"));
if (genreId <= 0L) {
return repository.findAll(pageable);
} else {
return repository.findByGenreId(genreId, pageable);
}
}
@Transactional(readOnly = true)
public List<FilmEntity> getByIds(Collection<Long> ids) {
final List<FilmEntity> films = StreamSupport.stream(repository.findAllById(ids).spliterator(), false).toList();

View File

@ -10,7 +10,9 @@ public class UserfilmDto {
private String filmName;
@NotNull
@Min(1)
private Long filmId;
private String genreName;
private String imageExtension;
public Long getId() {
return id;
@ -35,4 +37,20 @@ public class UserfilmDto {
public void setGenreName(String genreName) {
this.genreName = genreName;
}
public String getImageExtension() {
return imageExtension;
}
public void setImageExtension(String imageExtension) {
this.imageExtension = imageExtension;
}
public long getFilmId() {
return filmId;
}
public void setFilmId(Long filmId) {
this.filmId = filmId;
}
}

View File

@ -1,15 +1,15 @@
package com.example.demo.userfilms.api;
public class UserfilmGroupedDto {
private Long filmCount;
private Long Count;
private String genreName;
public Long getfilmCount() {
return filmCount;
public Long getCount() {
return Count;
}
public void setfilmCount(Long filmCount) {
this.filmCount = filmCount;
public void setCount(Long Count) {
this.Count = Count;
}
public String getGenreName() {

View File

@ -65,8 +65,7 @@ public class UserfilmEntity extends BaseEntity {
if (obj == null || getClass() != obj.getClass())
return false;
final UserfilmEntity other = (UserfilmEntity) obj;
return Objects.equals(other.getId(), id)
&& Objects.equals(other.getFilm(), film)
return Objects.equals(other.getFilm().getId(), film.getId())
&& Objects.equals(other.getUser().getId(), user.getId());
}
}

View File

@ -8,6 +8,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import com.example.demo.userfilms.model.UserfilmEntity;
import com.example.demo.userfilms.model.UserfilmGrouped;
@ -25,6 +26,14 @@ public interface UserfilmRepository
Page<UserfilmEntity> findByUserIdAndGenreId(long userId, long genreId, Pageable pageable);
@Query("select uf from UserfilmEntity uf " +
"join uf.film f " +
"join uf.user u " +
"join uf.genre g " +
"where f.name like CONCAT(:filmName, '%') and u.id = :userId")
Page<UserfilmEntity> findUserfilmsByFilmNameAndUserId(
@Param("userId") Long userId, @Param("filmName") String filmName, Pageable pageable);
@Query("select "
+ "count(*) as count, "
+ "g as genre "

View File

@ -27,6 +27,14 @@ public class UserfilmService {
this.userService = userService;
}
public boolean CheckContains(List<UserfilmEntity> list, UserfilmEntity entity) {
for (UserfilmEntity userfilmEntity : list) {
if (userfilmEntity.equals(entity))
return true;
}
return false;
}
@Transactional(readOnly = true)
public List<UserfilmEntity> getAll(long userId, long genreId) {
userService.get(userId);
@ -54,6 +62,17 @@ public class UserfilmService {
}
}
@Transactional(readOnly = true)
public Page<UserfilmEntity> getAll(long userId, String filmName, int page, int size) {
final Pageable pageable = PageRequest.of(page, size, Sort.by("id"));
userService.get(userId);
if (filmName.isEmpty()) {
return repository.findByUserId(userId, pageable);
} else {
return repository.findUserfilmsByFilmNameAndUserId(userId, filmName, pageable);
}
}
@Transactional(readOnly = true)
public UserfilmEntity get(long userId, long id) {
userService.get(userId);
@ -68,6 +87,9 @@ public class UserfilmService {
}
final UserEntity existsUser = userService.get(userId);
entity.setUser(existsUser);
if (CheckContains(getAll(userId), entity)) {
throw new IllegalArgumentException("Entity allready exists");
}
return repository.save(entity);
}

View File

@ -1,5 +1,6 @@
package com.example.demo.users.api;
import java.util.List;
import java.util.stream.Collectors;
import org.modelmapper.ModelMapper;
@ -16,9 +17,15 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.nio.file.Path;
import com.example.demo.core.file.FileHelper;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.films.api.FilmController;
import com.example.demo.films.api.FilmDto;
import com.example.demo.films.model.FilmEntity;
import com.example.demo.films.service.FilmService;
import com.example.demo.genres.api.GenreDto;
import com.example.demo.genres.model.GenreEntity;
@ -32,6 +39,7 @@ import com.example.demo.users.model.UserSubscriptionWithStatus;
import com.example.demo.users.service.UserService;
import jakarta.validation.Valid;
import jakarta.servlet.ServletContext;
@Controller
@RequestMapping("/cabinet")
@ -41,6 +49,7 @@ public class UserCabinetController {
private static final String PAGE_ATTRIBUTE = "page";
private static final String GENREID_ATTRIBUTE = "genreId";
private static final String FILMNAME_ATTRIBUTE = "filmName";
private static final String PROFILE_ATTRIBUTE = "profile";
private final UserfilmService userfilmService;
@ -67,29 +76,56 @@ public class UserCabinetController {
}
private UserfilmDto toUserfilmDto(UserfilmEntity entity) {
return modelMapper.map(entity, UserfilmDto.class);
final UserfilmDto dto = modelMapper.map(entity, UserfilmDto.class);
dto.setImageExtension(entity.getFilm().getImageExtension());
dto.setFilmId(entity.getFilm().getId());
return dto;
}
private UserfilmGroupedDto toUserfilmGroupedDto(UserfilmGrouped entity) {
return modelMapper.map(entity, UserfilmGroupedDto.class);
}
private UserSubscriptionDto tSubscriptionDto(UserSubscriptionWithStatus entity) {
return modelMapper.map(entity, UserSubscriptionDto.class);
}
private UserSubscriptionWithStatus toSubscriptionWithStatus(UserSubscriptionDto dto) {
return modelMapper.map(dto, UserSubscriptionWithStatus.class);
}
@GetMapping()
public String getProfile(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@RequestParam(name = GENREID_ATTRIBUTE, defaultValue = "0") int genreId,
@RequestParam(name = FILMNAME_ATTRIBUTE, defaultValue = "") String filmName,
Model model,
@AuthenticationPrincipal UserPrincipal principal) {
final long userId = principal.getId();
model.addAttribute(PAGE_ATTRIBUTE, page);
model.addAttribute(GENREID_ATTRIBUTE, genreId);
model.addAllAttributes(PageAttributesMapper.toAttributes(
userfilmService.getAll(userId, genreId, page, Constants.DEFUALT_PAGE_SIZE),
this::toUserfilmDto));
model.addAttribute(FILMNAME_ATTRIBUTE, filmName);
if (genreId <= 0) {
model.addAllAttributes(PageAttributesMapper.toAttributes(
userfilmService.getAll(userId, filmName, page, Constants.DEFUALT_PAGE_SIZE),
this::toUserfilmDto));
} else {
model.addAllAttributes(PageAttributesMapper.toAttributes(
userfilmService.getAll(userId, genreId, page, Constants.DEFUALT_PAGE_SIZE),
this::toUserfilmDto));
}
model.addAttribute("stats",
userfilmService.getTotal(userId).stream()
.map(this::toUserfilmGroupedDto)
.toList());
model.addAttribute("genres",
genreService.getAll().stream()
.map(this::toGenreDto)
.toList());
model.addAttribute(PROFILE_ATTRIBUTE,
new UserProfileDto(userService.getUserSubscriptions(userId).stream()
.map(this::tSubscriptionDto)
.toList()));
return PROFILE_VIEW;
}
@ -105,4 +141,17 @@ public class UserCabinetController {
userfilmService.delete(principal.getId(), id);
return Constants.REDIRECT_VIEW + "/cabinet";
}
@PostMapping("/add/{id}")
public String addUserfilm(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@RequestParam(name = GENREID_ATTRIBUTE, defaultValue = "0") int genreId,
RedirectAttributes redirectAttributes,
@AuthenticationPrincipal UserPrincipal principal) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
redirectAttributes.addAttribute(GENREID_ATTRIBUTE, genreId);
userfilmService.create(principal.getId(), new UserfilmEntity(filmService.get(id)));
return Constants.REDIRECT_VIEW + "/cabinet";
}
}

View File

@ -0,0 +1,101 @@
package com.example.demo.users.api;
import java.util.stream.Collectors;
import org.modelmapper.ModelMapper;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.nio.file.Path;
import com.example.demo.core.file.FileHelper;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.films.api.FilmController;
import com.example.demo.films.api.FilmDto;
import com.example.demo.films.model.FilmEntity;
import com.example.demo.films.service.FilmService;
import com.example.demo.genres.api.GenreDto;
import com.example.demo.genres.model.GenreEntity;
import com.example.demo.genres.service.GenreService;
import com.example.demo.userfilms.api.UserfilmDto;
import com.example.demo.userfilms.api.UserfilmGroupedDto;
import com.example.demo.userfilms.model.UserfilmEntity;
import com.example.demo.userfilms.model.UserfilmGrouped;
import com.example.demo.userfilms.service.UserfilmService;
import com.example.demo.users.model.UserSubscriptionWithStatus;
import com.example.demo.users.service.UserService;
@Controller
@RequestMapping("/allfilms")
public class UserFilmController {
private static final String PROFILE_VIEW = "allfilms";
private static final String PAGE_ATTRIBUTE = "page";
private static final String GENREID_ATTRIBUTE = "genreId";
private static final String PROFILE_ATTRIBUTE = "profile";
private final UserfilmService userfilmService;
private final GenreService genreService;
private final FilmService filmService;
private final UserService userService;
private final ModelMapper modelMapper;
public UserFilmController(
UserfilmService userfilmService,
GenreService genreService,
FilmService filmService,
UserService userService,
ModelMapper modelMapper) {
this.userfilmService = userfilmService;
this.genreService = genreService;
this.filmService = filmService;
this.userService = userService;
this.modelMapper = modelMapper;
}
private GenreDto toGenreDto(GenreEntity entity) {
return modelMapper.map(entity, GenreDto.class);
}
private FilmDto toFilmDto(FilmEntity entity) {
return modelMapper.map(entity, FilmDto.class);
}
private UserfilmDto toUserfilmDto(UserfilmEntity entity) {
return modelMapper.map(entity, UserfilmDto.class);
}
private UserfilmGroupedDto toUserfilmGroupedDto(UserfilmGrouped entity) {
return modelMapper.map(entity, UserfilmGroupedDto.class);
}
@GetMapping()
public String getProfile(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@RequestParam(name = GENREID_ATTRIBUTE, defaultValue = "0") int genreId,
Model model,
@AuthenticationPrincipal UserPrincipal principal) {
model.addAttribute(PAGE_ATTRIBUTE, page);
model.addAttribute(GENREID_ATTRIBUTE, genreId);
model.addAllAttributes(PageAttributesMapper.toAttributes(
filmService.getAll(genreId, page, Constants.DEFUALT_PAGE_SIZE),
this::toFilmDto));
model.addAttribute("genres",
genreService.getAll().stream()
.map(this::toGenreDto)
.toList());
return PROFILE_VIEW;
}
}

View File

@ -38,7 +38,7 @@ td form {
}
.my-navbar {
background-color: #3c3c3c !important;
background-color: #1b1515 !important;
}
.my-navbar .link a:hover {
@ -117,4 +117,15 @@ td form {
#chat .message .message-in {
background-color: rgba(57, 192, 237, 0.1);
}
.image-container {
margin: 0 20px;
display: block;
border-radius: 32px;
overflow: hidden;
}
.image-container img {
width: 100%;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@
<body class="h-100 d-flex flex-column">
<nav class="navbar navbar-expand-md my-navbar" data-bs-theme="dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<a class="navbar-brand" href="/cabinet">
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
MyShop
</a>
@ -34,10 +34,10 @@
th:classappend="${activeLink.startsWith('/admin/user') ? 'active' : ''}">
Пользователи
</a>
<a class="nav-link" href="/admin/type"
<!-- <a class="nav-link" href="/admin/type"
th:classappend="${activeLink.startsWith('/admin/type') ? 'active' : ''}">
Типы заказов
</a>
</a> -->
<a class="nav-link" href="/admin/genre"
th:classappend="${activeLink.startsWith('/admin/genre') ? 'active' : ''}">
Жанры фильмов
@ -61,16 +61,16 @@
Выход ([[${userName}]])
</button>
</form>
<a class="navbar-brand" href="/cart">
<!-- <a class="navbar-brand" href="/cart">
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
[[${#numbers.formatDecimal(totalCart, 1, 2)}]] &#8381;
</a>
</a> -->
</ul>
</div>
</th:block>
</div>
</nav>
<main class="container-fluid p-2" layout:fragment="content">
<main class="container-fluid p-1" layout:fragment="content">
</main>
<section sec:authorize="isAuthenticated()" id="chat-container" class="row d-flex justify-content-center">
<div class="col-12 p-0 m-0">

View File

@ -2,30 +2,36 @@
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редакторовать тип заказа</title>
<title>Редакторовать фильм</title>
</head>
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/admin/film/edit/{id}(id=${film.id})}" th:object="${film}" method="post">
<form action="#" th:action="@{/admin/film/edit/{id}(id=${film.id})}" th:object="${film}" method="post"
enctype="multipart/form-data">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Жанр фильма</label>
<label for="name" class="form-label">Фильма</label>
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<select th:name="genreId" id="genreId" class="form-select">
<option selected value="">Фильтр по типу товара</option>
<select th:name="genreId" th:field="*{genreId}" id="genreId" class="form-select">
<option selected value="">Выберите жанр фильма</option>
<option th:each="genre : ${genres}" th:value="${genre.id}" th:selected="${genre.id==genreId}">
[[${genre.name}]]
</option>
<div th:if="${#fields.hasErrors('genreId')}" th:errors="*{genreId}" class="invalid-feedback"></div>
</select>
</div>
<div class="mb-2">
<label for="image" class="form-label">Изображение</label>
<input type="file" th:field="*{image}" id="image" class="form-control">
<div th:if="${#fields.hasErrors('image')}" th:errors="*{image}" class="invalid-feedback"></div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" href="/admin/film">Отмена</a>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long