Build raw service list view, refactor mixins
This commit is contained in:
parent
f0e34c2419
commit
8484fef8f2
6 changed files with 98 additions and 18 deletions
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends "frontend/base.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
|
{% block html_title %}
|
||||||
|
{% block page_title %}
|
||||||
|
{% translate "Services" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
{% endblock html_title %}
|
||||||
|
{% block card_content %}
|
||||||
|
<form class="search-form" auto-submit>
|
||||||
|
{{ filter_form }}
|
||||||
|
</form>
|
||||||
|
{% for service in services %}
|
||||||
|
<div class="col-12 col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
{% if service.logo %}
|
||||||
|
<img src="{{ service.logo.url }}"
|
||||||
|
alt="{{ service.name }}"
|
||||||
|
class="me-3"
|
||||||
|
style="max-width: 48px;
|
||||||
|
max-height: 48px">
|
||||||
|
{% endif %}
|
||||||
|
<h5 class="card-title mb-0">{{ service.name }}</h5>
|
||||||
|
</div>
|
||||||
|
{% if service.description %}<p class="card-text">{{ service.description }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer d-flex justify-content-end">
|
||||||
|
<a href="#" class="btn btn-primary">{% translate "View Details" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p>{% translate "No services found." %}</p>
|
||||||
|
{% endfor %}
|
||||||
|
<script src="{% static "js/autosubmit.js" %}" defer></script>
|
||||||
|
{% endblock card_content %}
|
|
@ -20,6 +20,11 @@ urlpatterns = [
|
||||||
views.OrganizationUpdateView.as_view(),
|
views.OrganizationUpdateView.as_view(),
|
||||||
name="organization.details",
|
name="organization.details",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"services/",
|
||||||
|
views.OrganizationServicesView.as_view(),
|
||||||
|
name="organization.services",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"",
|
"",
|
||||||
views.OrganizationDashboardView.as_view(),
|
views.OrganizationDashboardView.as_view(),
|
||||||
|
|
|
@ -5,6 +5,7 @@ from .organization import (
|
||||||
OrganizationDashboardView,
|
OrganizationDashboardView,
|
||||||
OrganizationUpdateView,
|
OrganizationUpdateView,
|
||||||
)
|
)
|
||||||
|
from .service import OrganizationServicesView
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"IndexView",
|
"IndexView",
|
||||||
|
@ -12,5 +13,6 @@ __all__ = [
|
||||||
"OrganizationCreateView",
|
"OrganizationCreateView",
|
||||||
"OrganizationDashboardView",
|
"OrganizationDashboardView",
|
||||||
"OrganizationUpdateView",
|
"OrganizationUpdateView",
|
||||||
|
"OrganizationServicesView",
|
||||||
"ProfileView",
|
"ProfileView",
|
||||||
]
|
]
|
||||||
|
|
|
@ -57,3 +57,29 @@ class HtmxUpdateView(AutoPermissionRequiredMixin, UpdateView):
|
||||||
if self.is_htmx and self._get_fragment():
|
if self.is_htmx and self._get_fragment():
|
||||||
return self.get(self.request, *self.args, **self.kwargs)
|
return self.get(self.request, *self.args, **self.kwargs)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationViewMixin(PermissionRequiredMixin):
|
||||||
|
model = Organization
|
||||||
|
context_object_name = "organization"
|
||||||
|
permission_required = "core.view_organization"
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def organization(self):
|
||||||
|
return self.request.organization
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.organization
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def object(self):
|
||||||
|
return self.get_object()
|
||||||
|
|
||||||
|
def get_permission_object(self):
|
||||||
|
return self.organization
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
return (
|
||||||
|
self.request.user.has_perm("core.view_organization", self.organization)
|
||||||
|
and super().has_permission()
|
||||||
|
)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.functional import cached_property
|
|
||||||
from django.views.generic import CreateView, DetailView
|
from django.views.generic import CreateView, DetailView
|
||||||
from rules.contrib.views import AutoPermissionRequiredMixin
|
from rules.contrib.views import AutoPermissionRequiredMixin
|
||||||
|
|
||||||
from servala.core.models import Organization
|
from servala.core.models import Organization
|
||||||
from servala.frontend.forms import OrganizationForm
|
from servala.frontend.forms import OrganizationForm
|
||||||
from servala.frontend.views.mixins import HtmxUpdateView
|
from servala.frontend.views.mixins import HtmxUpdateView, OrganizationViewMixin
|
||||||
|
|
||||||
|
|
||||||
class OrganizationCreateView(AutoPermissionRequiredMixin, CreateView):
|
class OrganizationCreateView(AutoPermissionRequiredMixin, CreateView):
|
||||||
|
@ -20,22 +19,6 @@ class OrganizationCreateView(AutoPermissionRequiredMixin, CreateView):
|
||||||
return redirect(instance.urls.base)
|
return redirect(instance.urls.base)
|
||||||
|
|
||||||
|
|
||||||
class OrganizationViewMixin:
|
|
||||||
model = Organization
|
|
||||||
context_object_name = "organization"
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def organization(self):
|
|
||||||
return self.request.organization
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return self.organization
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def object(self):
|
|
||||||
return self.get_object()
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationDashboardView(
|
class OrganizationDashboardView(
|
||||||
AutoPermissionRequiredMixin, OrganizationViewMixin, DetailView
|
AutoPermissionRequiredMixin, OrganizationViewMixin, DetailView
|
||||||
):
|
):
|
||||||
|
|
25
src/servala/static/js/autosubmit.js
Normal file
25
src/servala/static/js/autosubmit.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Auto-submit functionality for forms
|
||||||
|
*
|
||||||
|
* This script looks for forms with the 'auto-submit' attribute
|
||||||
|
* and automatically submits them when any input, select, or textarea
|
||||||
|
* within the form changes, useful for search/filter forms.
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('form[auto-submit]').forEach(form => {
|
||||||
|
const formElements = form.querySelectorAll('input, select, textarea')
|
||||||
|
|
||||||
|
formElements.forEach(element => {
|
||||||
|
if (element.type === 'checkbox' || element.type === 'radio') {
|
||||||
|
element.addEventListener('change', () => {
|
||||||
|
form.submit()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (element.tagName.toLowerCase() === 'select') {
|
||||||
|
element.addEventListener('change', () => {
|
||||||
|
form.submit()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Add table
Add a link
Reference in a new issue