Inline-edit user profile with HTMX
This commit is contained in:
parent
ecf4052819
commit
72bd7388d6
4 changed files with 107 additions and 14 deletions
|
@ -1,3 +1,4 @@
|
||||||
from .organization import OrganizationCreateForm
|
from .organization import OrganizationCreateForm
|
||||||
|
from .profile import UserProfileForm
|
||||||
|
|
||||||
__all__ = ["OrganizationCreateForm"]
|
__all__ = ["OrganizationCreateForm", "UserProfileForm"]
|
||||||
|
|
11
src/servala/frontend/forms/profile.py
Normal file
11
src/servala/frontend/forms/profile.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from servala.core.models import User
|
||||||
|
from servala.frontend.forms.mixins import HtmxMixin
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfileForm(HtmxMixin, forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ("email", "company")
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends "frontend/base.html" %}
|
{% extends "frontend/base.html" %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
|
{% load partials %}
|
||||||
{% block html_title %}
|
{% block html_title %}
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
{% translate "Profile" %}
|
{% translate "Profile" %}
|
||||||
|
@ -10,6 +11,70 @@
|
||||||
<h4 class="card-title">{% translate "Account" %}</h4>
|
<h4 class="card-title">{% translate "Account" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% partialdef user-email %}
|
||||||
|
<tr>
|
||||||
|
<th>{% translate "E-mail" %}</th>
|
||||||
|
<td>{{ request.user.email }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-primary"
|
||||||
|
hx-get="{% url 'frontend:profile' %}?fragment=user-email-edit"
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML">{% translate "Edit" %}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endpartialdef user-email %}
|
||||||
|
{% partialdef user-company %}
|
||||||
|
<tr>
|
||||||
|
<th>{% translate "Company" %}</th>
|
||||||
|
<td>{{ request.user.company|default_if_none:"" }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-primary"
|
||||||
|
hx-get="{% url 'frontend:profile' %}?fragment=user-company-edit"
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML">{% translate "Edit" %}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endpartialdef user-company %}
|
||||||
|
{% partialdef user-email-edit %}
|
||||||
|
<tr>
|
||||||
|
<th>{% translate "E-mail" %}</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<form hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-post="{{ request.url }}">
|
||||||
|
{{ form.email }}
|
||||||
|
<input type="hidden" name="hx-single-field" value="email">
|
||||||
|
<input type="hidden" name="fragment" value="user-email">
|
||||||
|
<button type="submit" class="btn btn-primary">{% translate "Save" %}</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
hx-get="{{ request.path }}?fragment=user-email"
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML">{% translate "Cancel" %}</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endpartialdef %}
|
||||||
|
{% partialdef user-company-edit %}
|
||||||
|
<tr>
|
||||||
|
<th>{% translate "Company" %}</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<form hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-post="{{ request.url }}">
|
||||||
|
{{ form.company }}
|
||||||
|
<input type="hidden" name="hx-single-field" value="company">
|
||||||
|
<input type="hidden" name="fragment" value="user-company">
|
||||||
|
<button type="submit" class="btn btn-primary">{% translate "Save" %}</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
hx-get="{{ request.path }}?fragment=user-company"
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML">{% translate "Cancel" %}</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endpartialdef %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section>
|
<section>
|
||||||
<div class="row match-height">
|
<div class="row match-height">
|
||||||
|
@ -23,22 +88,18 @@
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-lg">
|
<table class="table table-lg">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{% partial user-email %}
|
||||||
<th>{% translate "E-mail" %}</th>
|
|
||||||
<td>{{ request.user.email }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "First name" %}</th>
|
<th>{% translate "First name" %}</th>
|
||||||
<td>{{ request.user.first_name }}</td>
|
<td>{{ request.user.first_name }}</td>
|
||||||
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Last name" %}</th>
|
<th>{% translate "Last name" %}</th>
|
||||||
<td>{{ request.user.last_name }}</td>
|
<td>{{ request.user.last_name }}</td>
|
||||||
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
{% partial user-company %}
|
||||||
<th>{% translate "Company" %}</th>
|
|
||||||
<td>{{ request.user.company }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,21 +1,41 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.views.generic import TemplateView
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from django.views.generic import TemplateView, UpdateView
|
||||||
|
|
||||||
|
from servala.core.models import User
|
||||||
|
from servala.frontend.forms.profile import UserProfileForm
|
||||||
|
from servala.frontend.views.mixins import HtmxMixin
|
||||||
|
|
||||||
|
|
||||||
class IndexView(TemplateView):
|
class IndexView(TemplateView):
|
||||||
template_name = "frontend/index.html"
|
template_name = "frontend/index.html"
|
||||||
|
|
||||||
|
|
||||||
class ProfileView(TemplateView):
|
class ProfileView(HtmxMixin, UpdateView):
|
||||||
template_name = "frontend/profile.html"
|
template_name = "frontend/profile.html"
|
||||||
|
form_class = UserProfileForm
|
||||||
|
success_url = reverse_lazy("frontend:profile")
|
||||||
|
fragments = ("user-email", "user-email-edit", "user-company", "user-company-edit")
|
||||||
|
model = User
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def object(self):
|
||||||
|
return self.get_object()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
keycloak_server_url = settings.SOCIALACCOUNT_PROVIDERS["openid_connect"][
|
keycloak_settings = settings.SOCIALACCOUNT_PROVIDERS["openid_connect"]
|
||||||
"APPS"
|
keycloak_server_url = keycloak_settings["APPS"][0]["settings"]["server_url"]
|
||||||
][0]["settings"]["server_url"]
|
|
||||||
account_url = keycloak_server_url.replace(
|
account_url = keycloak_server_url.replace(
|
||||||
"/.well-known/openid-configuration", "/account"
|
"/.well-known/openid-configuration", "/account"
|
||||||
)
|
)
|
||||||
context["account_href"] = account_url
|
context["account_href"] = account_url
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.save()
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue