feat: enhance UI and extend form functionality

This update brings several enhancements to the user interface and
extends the forms functionality across the application. Specifically,
the changes include the introduction of `django-crispy-forms` and
`crispy-bootstrap5` to improve form aesthetics and usability
significantly. Additionally, the admin area now features enhanced link
color for better visibility, and user interface improvements have been
made across various templates, like adding direct edit and delete
category links and more intuitive navigation options for better user
experience. Moreover, the inclusion of new forms for Scene, Category,
and Original Media creation aligns with the app's need for structured
data entry and complements the existing models by ensuring a more
user-friendly interaction with the database.

Key changes include:
- Introduction of `django-crispy-forms` and `crispy-bootstrap5` for
better form rendering.
- UI enhancements for clarity and ease of use in the admin area and
templates.
- New forms for Scene, Category, and Original Media to streamline
content creation processes.

These changes aim to improve both the appearance and functionality of
the application, making it more appealing and accessible to users while
facilitating easier content management.
This commit is contained in:
Kumi 2024-03-27 09:22:04 +01:00
parent 0471f151b6
commit 42e9de6942
Signed by: kumi
GPG key ID: ECBCC9082395383F
13 changed files with 123 additions and 12 deletions

View file

@ -227,3 +227,7 @@ table.dataTable {
.error-message {
color: red;
}
.admin-header a {
color: #9cdcfe;
}

View file

@ -30,6 +30,8 @@ boto3 = "*"
argon2-cffi = "*"
django-csp = "*"
django-rest-polymorphic = "*"
django-crispy-forms = "*"
crispy-bootstrap5 = "*"
[tool.poetry.group.mysql.dependencies]
mysqlclient = "*"

View file

@ -39,6 +39,8 @@ INSTALLED_APPS = [
"django_celery_results",
"drf_spectacular",
"drf_spectacular_sidecar",
"crispy_forms",
"crispy_bootstrap5",
"quackscape",
"quackscape.users",
"quackscape.tours",
@ -218,7 +220,7 @@ QUACKSCAPE_CONTENT_RESOLUTIONS = [
(65536, 32768),
]
MAX_IMAGE_PIXELS = 34359738368 # 262144x131072 - should be enough, no?
MAX_IMAGE_PIXELS = 34359738368 # 262144x131072 - should be enough, no?
# ffmpeg settings
@ -249,3 +251,8 @@ FFMPEG_DEFAULT_OPTION = ASK.config.get("ffmpeg", "DefaultOption", fallback="defa
LOGIN_URL = reverse_lazy("quackscape.users:login")
LOGIN_REDIRECT_URL = reverse_lazy("quackscape.users:categories")
LOGOUT_REDIRECT_URL = reverse_lazy("quackscape.users:login")
# Crispy forms settings
CRISPY_ALLOWED_TEMPLATE_PACKS = {"bootstrap5"}
CRISPY_TEMPLATE_PACK = "bootstrap5"

21
quackscape/tours/forms.py Normal file
View file

@ -0,0 +1,21 @@
from django.forms import ModelForm
from .models import Scene, Element, Category, OriginalMedia
class SceneForm(ModelForm):
class Meta:
model = Scene
fields = ("title", "description")
class CategoryForm(ModelForm):
class Meta:
model = Category
fields = ("title", "description")
class OriginalMediaForm(ModelForm):
class Meta:
model = OriginalMedia
fields = ("title", "description")

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2024-03-27 07:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tours', '0017_originalvideo_thumbnail'),
]
operations = [
migrations.AddField(
model_name='category',
name='description',
field=models.TextField(blank=True, null=True),
),
]

View file

@ -22,6 +22,7 @@ def upload_to(instance, filename):
class Category(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
title = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
owner = models.ForeignKey(
get_user_model(),
related_name="owned_categories",

View file

@ -11,7 +11,8 @@
<h2>Quackscape</h2>
<div class="user-info">
{% if user.is_authenticated %}
<p>Logged in as <strong>{{ user.email }}</strong></p>
<p>Logged in as <strong>{{ user.email }}</strong>
<a href="{% url "quackscape.users:logout" %}">Logout</a></p>
{% else %}
<p>Not logged in</p>
{% endif %}

View file

@ -15,7 +15,7 @@
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center">
<h4>Available categories</h4>
<button type="button" class="btn btn-primary">Create category</button>
<a href="{% url "quackscape.users:category-create" %}" class="btn btn-primary">Create category</a>
</div>
<ul>
{% for category in categories %}

View file

@ -5,7 +5,14 @@
<button type="button" class="btn btn-danger">End category membership</button>
{% endif %}
{% if request.user.is_superuser or request.user == category.owner %}
<button type="button" class="btn btn-danger">Delete category</button>
<div>
<a href="/tours/category/{{ category.id }}/edit/" class="btn btn-primary"
>Edit category</a
>
<a href="/tours/category/{{ category.id }}/delete/" class="btn btn-danger"
>Delete category</a
>
</div>
{% endif %}
</div>
@ -142,7 +149,9 @@
<h5>User permissions</h5>
<div>
<button type="button" class="btn btn-primary">Invite user</button>
<button type="button" class="btn btn-primary">Transfer ownership</button>
<button type="button" class="btn btn-primary">
Transfer ownership
</button>
</div>
</div>
<table id="permissionsTable" class="display">

View file

@ -0,0 +1,27 @@
{% extends "users/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="mb-0">{{ title }}</h4>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }} {% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %} {{ error }} {% endfor %}
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -6,7 +6,7 @@
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Login</h4>
<h4 class="mb-0">{{ title }}</h4>
</div>
<div class="card-body">
<form method="post" action="{% url 'quackscape.users:login' %}">

View file

@ -1,4 +1,4 @@
from .views import UserAreaMainView, CategoriesView, CategoryView, FileUploadView, Login, Logout
from .views import UserAreaMainView, CategoriesView, CategoryView, FileUploadView, Login, Logout, CategoryCreateView
from django.urls import path
@ -7,6 +7,7 @@ app_name = 'quackscape.users'
urlpatterns = [
path('', UserAreaMainView.as_view(), name='user-area-main'),
path('categories/', CategoriesView.as_view(), name='categories'),
path('category/create/', CategoryCreateView.as_view(), name='category-create'),
path('category/<uuid:category>/', CategoryView.as_view(), name='category'),
path('category/<uuid:category>/upload/', FileUploadView.as_view(), name='media-upload'),
path('login/', Login.as_view(), name='login'),

View file

@ -1,7 +1,15 @@
from django.views.generic import TemplateView, ListView, DetailView
from django.views.generic import (
TemplateView,
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView,
)
from django.http import Http404
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import reverse_lazy
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
@ -14,6 +22,7 @@ from quackscape.tours.serializers import (
OriginalImageSerializer,
OriginalVideoSerializer,
)
from quackscape.tours.forms import CategoryForm
class TitleMixin:
@ -63,9 +72,20 @@ class MediaUploadView(LoginRequiredMixin, TitleMixin, TemplateView):
title = "Upload Media"
class CategoryCreateView(LoginRequiredMixin, TitleMixin, TemplateView):
template_name = "users/category_create.html"
class CategoryCreateView(LoginRequiredMixin, TitleMixin, CreateView):
template_name = "users/generic_form.html"
title = "Create Category"
form_class = CategoryForm
model = Category
def form_valid(self, form):
form.instance.owner = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy(
"quackscape.users:category", kwargs={"category": self.object.id}
)
class FileUploadView(LoginRequiredMixin, GenericAPIView):