Compare commits
No commits in common. "2baa3fd5ec81fd44a3d678940f5b6cfd6119b796" and "0d09d338e847dbdabf993bf47d83a6faa5654665" have entirely different histories.
2baa3fd5ec
...
0d09d338e8
19 changed files with 30 additions and 295 deletions
|
@ -27,12 +27,6 @@ class Organization(ServalaModelMixin, models.Model):
|
||||||
verbose_name=_("Members"),
|
verbose_name=_("Members"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_owner(self, user):
|
|
||||||
OrganizationMembership.objects.filter(user=user, organization=self).delete()
|
|
||||||
OrganizationMembership.objects.create(
|
|
||||||
user=user, organization=self, role=OrganizationRole.OWNER
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Organization")
|
verbose_name = _("Organization")
|
||||||
verbose_name_plural = _("Organizations")
|
verbose_name_plural = _("Organizations")
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
def add_organizations(request):
|
|
||||||
if not request.user.is_authenticated:
|
|
||||||
return {"user_organizations": []}
|
|
||||||
|
|
||||||
return {"user_organizations": request.user.organizations.all()}
|
|
|
@ -1,3 +0,0 @@
|
||||||
from .organization import OrganizationCreateForm
|
|
||||||
|
|
||||||
__all__ = ["OrganizationCreateForm"]
|
|
|
@ -1,9 +0,0 @@
|
||||||
from django.forms import ModelForm
|
|
||||||
|
|
||||||
from servala.core.models import Organization
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationCreateForm(ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Organization
|
|
||||||
fields = ("name",)
|
|
|
@ -1,28 +0,0 @@
|
||||||
from django.forms.renderers import TemplatesSetting
|
|
||||||
|
|
||||||
|
|
||||||
def inject_class(f, class_name):
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
result = f(*args, **kwargs)
|
|
||||||
class_list = result.get("class", "")
|
|
||||||
class_list = f"{class_list} {class_name}".strip()
|
|
||||||
result["class"] = class_list
|
|
||||||
return result
|
|
||||||
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
class VerticalFormRenderer(TemplatesSetting):
|
|
||||||
form_template_name = "frontend/forms/form.html"
|
|
||||||
field_template_name = "frontend/forms/vertical_field.html"
|
|
||||||
|
|
||||||
def render(self, template_name, context, request=None):
|
|
||||||
if field := context.get("field"):
|
|
||||||
if field.field.widget.input_type == "checkbox":
|
|
||||||
class_name = "form-check-input"
|
|
||||||
else:
|
|
||||||
class_name = "form-control"
|
|
||||||
field.build_widget_attrs = inject_class(
|
|
||||||
field.build_widget_attrs, class_name
|
|
||||||
)
|
|
||||||
return super().render(template_name, context, request)
|
|
|
@ -1,53 +0,0 @@
|
||||||
{% extends "frontend/base.html" %}
|
|
||||||
{% load static i18n %}
|
|
||||||
{% load allauth account socialaccount %}
|
|
||||||
{% block html_title %}
|
|
||||||
{% block page_title %}
|
|
||||||
{% translate "Sign In" %}
|
|
||||||
{% endblock page_title %}
|
|
||||||
{% endblock html_title %}
|
|
||||||
{% block content %}
|
|
||||||
<section class="section">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="card-body">
|
|
||||||
{% if SOCIALACCOUNT_ENABLED %}
|
|
||||||
{% get_providers as socialaccount_providers %}
|
|
||||||
{% if socialaccount_providers %}
|
|
||||||
{% for provider in socialaccount_providers %}
|
|
||||||
{% provider_login_url provider process=process scope=scope auth_params=auth_params as href %}
|
|
||||||
<form method="post" action="{{ href }}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button href="{{ href }}"
|
|
||||||
class="btn btn-warning btn-lg icon icon-left"
|
|
||||||
title="{{ provider.name }}">
|
|
||||||
<img src="{% static 'img/keycloak.svg' %}" style="height: 30px">
|
|
||||||
<span class="mx-1">{% translate "Sign in with your" %} {{ provider.name }}</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<div class="mt-2">
|
|
||||||
<a data-bs-toggle="collapse"
|
|
||||||
href="#login-form"
|
|
||||||
role="button"
|
|
||||||
aria-controls="login-form"
|
|
||||||
class="d-flex align-items-center">
|
|
||||||
<i class="bi bi-chevron-right me-2 ms-1 mb-2 collapse-icon"></i>
|
|
||||||
{% translate "Log in with email and password instead" %}
|
|
||||||
</a>
|
|
||||||
<div class="collapse mt-3 ms-3"
|
|
||||||
id="login-form"
|
|
||||||
class="form form-vertical"
|
|
||||||
style="max-width: 400px">
|
|
||||||
{% url 'account_login' as form_action %}
|
|
||||||
{% translate "Sign In" as form_submit_label %}
|
|
||||||
{% include "includes/form.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock content %}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{% if errors %}
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
<div>
|
|
||||||
{% if errors|length > 1 %}
|
|
||||||
<ul>
|
|
||||||
{% for error in errors %}<li>{{ error }}</li>{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
|
||||||
{{ errors.0 }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-body">
|
|
||||||
<div class="row">
|
|
||||||
{% for field, errors in fields %}{{ field.as_field_group }}{% endfor %}
|
|
||||||
{% for field in hidden_fields %}{{ field }}{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,26 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-group{% with classes=field.css_classes %}{% if classes %} {{ classes }}{% endif %}{% endwith %}">
|
|
||||||
{% if field.field.widget.input_type != "checkbox" or field.field.widget.allow_multiple_selected %}
|
|
||||||
<label for="{{ field.auto_id }}" class="{{ label_class }}">
|
|
||||||
{{ field.label }}
|
|
||||||
{% if not field.field.required %}
|
|
||||||
<span class="optional">{% translate "Optional" %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
{% if field.use_fieldset %}
|
|
||||||
<fieldset {% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}>
|
|
||||||
{% endif %}
|
|
||||||
{{ field }}
|
|
||||||
{% if field.field.widget.input_type == "checkbox" and not field.field.widget.allow_multiple_selected %}
|
|
||||||
<label for="{{ field.auto_id }}">{{ field.label }}</label>
|
|
||||||
{% endif %}
|
|
||||||
{% if field.use_fieldset %}</fieldset>{% endif %}
|
|
||||||
{% for text in field.errors %}<div class="invalid-feedback">{{ text }}</div>{% endfor %}
|
|
||||||
{% if field.help_text %}
|
|
||||||
<small class="form-text text-muted"
|
|
||||||
{% if field.auto_id %}id="{{ field.auto_id }}_helptext"{% endif %}>{{ field.help_text|safe }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,16 +0,0 @@
|
||||||
{% extends "frontend/base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block html_title %}
|
|
||||||
{% block page_title %}
|
|
||||||
{% translate "Create a new organization" %}
|
|
||||||
{% endblock page_title %}
|
|
||||||
{% endblock html_title %}
|
|
||||||
{% block content %}
|
|
||||||
<section class="section">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="card-body">{% include "includes/form.html" %}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock content %}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<form class="form form-vertical"
|
|
||||||
method="post"
|
|
||||||
{% if form_action %}action="{{ form_action }}"{% endif %}>
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form }}
|
|
||||||
<button class="btn btn-primary" type="submit">
|
|
||||||
{% if form_submit_label %}
|
|
||||||
{{ form_submit_label }}
|
|
||||||
{% else %}
|
|
||||||
{% translate "Save" %}
|
|
||||||
{% endif %}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
|
@ -56,67 +56,42 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-menu">
|
<div class="sidebar-menu">
|
||||||
<ul class="menu">
|
<ul class="menu">
|
||||||
|
<li class="sidebar-item active">
|
||||||
|
<a href="index.html" class='sidebar-link'>
|
||||||
|
<i class="bi bi-grid-fill"></i>
|
||||||
|
<span>{% translate 'Dashboard' %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="sidebar-title">{% translate 'Account' %}</li>
|
||||||
{% if not request.user.is_authenticated %}
|
{% if not request.user.is_authenticated %}
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item has-sub">
|
||||||
<a href="{% url 'account_login' %}" class="sidebar-link">
|
<a href="#" class='sidebar-link'>
|
||||||
<i class="bi bi-person-badge-fill"></i>
|
<i class="bi bi-person-badge-fill"></i>
|
||||||
<span>{% translate 'Login' %}</span>
|
<span>{% translate 'Authentication' %}</span>
|
||||||
</a>
|
</a>
|
||||||
|
<ul class="submenu">
|
||||||
|
<li class="submenu-item">
|
||||||
|
<a href="{% url 'account_login' %}" class="submenu-link">{% translate 'Login' %}</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu-item">
|
||||||
|
<a href="{% url 'account_signup' %}" class="submenu-link">{% translate 'Register' %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# request.user.is_authenticated #}
|
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
{% if user_organizations.count > 1 %}
|
<a href="{% url 'profile' %}" class='sidebar-link'>
|
||||||
<button class="btn btn-primary dropdown-toggle me-1"
|
|
||||||
type="button"
|
|
||||||
id="organizationDropdown"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="false">
|
|
||||||
{% if current_organization %}
|
|
||||||
{{ current_organization.name }}
|
|
||||||
{% else %}
|
|
||||||
{% translate "Organizations" %}
|
|
||||||
{% endif %}
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="organizationDropdown">
|
|
||||||
{% for organization in user_organizations %}
|
|
||||||
<a class="dropdown-item" href="#TODO">{{ organization.name }}</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% elif current_organization %}
|
|
||||||
{% translate "Organization" %}: {{ current_organization.name }}
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'frontend:organization.create' %}" class="sidebar-link">
|
|
||||||
<i class="bi bi-plus-square"></i>
|
|
||||||
<span>{% translate "Create organization" %}</span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-item">
|
|
||||||
<a href="index.html" class='sidebar-link'>
|
|
||||||
<i class="bi bi-grid-fill"></i>
|
|
||||||
<span>{% translate 'Dashboard' %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-title">{% translate 'Account' %}</li>
|
|
||||||
<li class="sidebar-item">
|
|
||||||
<a href="{% url 'frontend:profile' %}" class='sidebar-link'>
|
|
||||||
<i class="bi bi-file-person"></i>
|
<i class="bi bi-file-person"></i>
|
||||||
<span>{% translate 'Profile' %}</span>
|
<span>{% translate 'Profile' %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<form action="{% url 'frontend:logout' %}" method="post">
|
<a href="{% url 'account_logout' %}" class='sidebar-link'>
|
||||||
{% csrf_token %}
|
<i class="bi bi-box-arrow-right"></i>
|
||||||
<button type="submit" class='sidebar-link btn'>
|
<span>{% translate 'Log out' %}</span>
|
||||||
<i class="bi bi-box-arrow-right"></i>
|
</a>
|
||||||
<span>{% translate 'Log out' %}</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# request.user.is_authenticated #}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from servala.frontend import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("accounts/profile/", views.ProfileView.as_view(), name="profile"),
|
|
||||||
path("accounts/logout/", views.LogoutView.as_view(), name="logout"),
|
|
||||||
path(
|
|
||||||
"organizations/create",
|
|
||||||
views.OrganizationCreateView.as_view(),
|
|
||||||
name="organization.create",
|
|
||||||
),
|
|
||||||
path("", views.IndexView.as_view(), name="index"),
|
|
||||||
]
|
|
|
@ -1,10 +0,0 @@
|
||||||
from .auth import LogoutView
|
|
||||||
from .generic import IndexView, ProfileView
|
|
||||||
from .organization import OrganizationCreateView
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"IndexView",
|
|
||||||
"LogoutView",
|
|
||||||
"OrganizationCreateView",
|
|
||||||
"ProfileView",
|
|
||||||
]
|
|
|
@ -1,12 +0,0 @@
|
||||||
from allauth.account.internal import flows
|
|
||||||
from allauth.account.utils import get_next_redirect_url
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(View):
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
flows.logout.logout(request)
|
|
||||||
url = get_next_redirect_url(request, "next") or "/"
|
|
||||||
return redirect(url)
|
|
|
@ -1,16 +0,0 @@
|
||||||
from django.views.generic import FormView
|
|
||||||
|
|
||||||
from servala.frontend.forms import OrganizationCreateForm
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationCreateView(FormView):
|
|
||||||
form_class = OrganizationCreateForm
|
|
||||||
template_name = "frontend/organizations/create.html"
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
form.save()
|
|
||||||
form.instance.set_owner(self.request.user)
|
|
||||||
return super().form_valid(form)
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return "/"
|
|
|
@ -95,7 +95,6 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"django.forms",
|
|
||||||
"servala.frontend",
|
"servala.frontend",
|
||||||
"allauth",
|
"allauth",
|
||||||
"allauth.account",
|
"allauth.account",
|
||||||
|
@ -113,9 +112,7 @@ MIDDLEWARE = [
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
"allauth.account.middleware.AccountMiddleware",
|
"allauth.account.middleware.AccountMiddleware",
|
||||||
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
|
||||||
]
|
]
|
||||||
LOGIN_URL = "account_login"
|
|
||||||
|
|
||||||
ROOT_URLCONF = "servala.urls"
|
ROOT_URLCONF = "servala.urls"
|
||||||
STATIC_URL = "static/" # CSS, JavaScript, etc.
|
STATIC_URL = "static/" # CSS, JavaScript, etc.
|
||||||
|
@ -147,23 +144,22 @@ TEMPLATES = [
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"django.template.context_processors.static",
|
"django.template.context_processors.static",
|
||||||
"servala.frontend.context_processors.add_organizations",
|
|
||||||
],
|
],
|
||||||
"loaders": template_loaders,
|
"loaders": template_loaders,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
FORM_RENDERER = "servala.frontend.forms.renderers.VerticalFormRenderer"
|
|
||||||
MESSAGE_TAGS = {
|
MESSAGE_TAGS = {
|
||||||
messages.ERROR: "danger",
|
messages.ERROR: "danger",
|
||||||
}
|
}
|
||||||
|
|
||||||
AUTH_USER_MODEL = "core.User"
|
AUTH_USER_MODEL = "core.User"
|
||||||
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
||||||
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
ACCOUNT_UNIQUE_EMAIL = True
|
ACCOUNT_UNIQUE_EMAIL = True
|
||||||
ACCOUNT_LOGIN_METHODS = {"email"}
|
ACCOUNT_USERNAME_REQUIRED = False
|
||||||
ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]
|
ACCOUNT_AUTHENTICATION_METHOD = "email"
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
# Needed to login by username in Django admin, regardless of `allauth`
|
# Needed to login by username in Django admin, regardless of `allauth`
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
<svg width="107" height="60" fill="none" xmlns="http://www.w3.org/2000/svg" class="image" aria-hidden="true">
|
|
||||||
<circle cx="53.002" cy="30" r="19" fill="#4CC3FF"></circle>
|
|
||||||
<path opacity="0.25" d="M67.833 41.874A18.92 18.92 0 0071.999 30c0-10.493-8.506-19-19-19a18.932 18.932 0 00-12.868 5.021c14.446.554 26.182 11.675 27.702 25.853z" fill="#000D1A"></path>
|
|
||||||
<path d="M31.594 6.491v8.571l5.217-3.173a31.165 31.165 0 0132.426 0l.622.378v.006l.14.078v-8.39l-.137-.07a38.382 38.382 0 00-36.807 1.712l-1.461.888zM74.436 53.508v-8.57l-5.218 3.173a31.165 31.165 0 01-32.425 0l-.625-.38v-.005L36 47.64v8.39l.168.079a38.382 38.382 0 0036.807-1.712l1.461-.889z" fill="#4CC3FF"></path>
|
|
||||||
<path d="M31.571 15.063v-8.57L0 25.694v8.614L31.571 53.51v-8.57L7.011 30l24.56-14.938zM74.432 53.511l31.571-19.202v-8.614L74.432 6.492v8.571l24.56 14.939-24.56 14.938v8.571z" fill="#000D1A"></path>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 893 B |
|
@ -6,7 +6,7 @@ from django.urls import path
|
||||||
from django.urls.conf import include
|
from django.urls.conf import include
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from servala.frontend import urls
|
from servala.frontend import views
|
||||||
|
|
||||||
admin.site.site_title = _("Servala Admin")
|
admin.site.site_title = _("Servala Admin")
|
||||||
admin.site.site_header = _("Servala Management")
|
admin.site.site_header = _("Servala Management")
|
||||||
|
@ -14,12 +14,13 @@ admin.site.index_title = _("Dashboard")
|
||||||
admin.site.login = secure_admin_login(admin.site.login)
|
admin.site.login = secure_admin_login(admin.site.login)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
|
||||||
path("", include((urls, "servala.frontend"), namespace="frontend")),
|
|
||||||
# This adds the allauth urls to the project:
|
# This adds the allauth urls to the project:
|
||||||
# - accounts/keycloak/login/
|
# - accounts/keycloak/login/
|
||||||
# - accounts/keycloak/login/callback/
|
# - accounts/keycloak/login/callback/
|
||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
|
path("accounts/profile/", views.ProfileView.as_view(), name="profile"),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("", views.IndexView.as_view(), name="index"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Serve static and media files in development
|
# Serve static and media files in development
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue