feat: mixin for update forms
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.db import models
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
class HTMXFormComponent(TemplateView):
|
||||||
|
form_class: Type[forms.ModelForm] = None
|
||||||
|
template_name: str = "components/default/form.html"
|
||||||
|
hx_trigger = ""
|
||||||
|
view_name = ""
|
||||||
|
|
||||||
|
def get_view_name(self):
|
||||||
|
return self.view_name
|
||||||
|
|
||||||
|
def get_form_class(self) -> Type[forms.ModelForm]:
|
||||||
|
return self.form_class
|
||||||
|
|
||||||
|
def get_form(self, initial=True):
|
||||||
|
instance = self.get_object()
|
||||||
|
|
||||||
|
if initial:
|
||||||
|
return self.form_class(initial=self.get_initial_values(instance), instance=instance)
|
||||||
|
|
||||||
|
return self.form_class(self.request.POST, instance=instance)
|
||||||
|
|
||||||
|
def get_object(self) -> models.Model:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_initial_values(self, instance):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"form": form,
|
||||||
|
"url": self.get_view_name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
form = self.get_form(initial=False)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
instance = form.save()
|
||||||
|
|
||||||
|
response = render(
|
||||||
|
request,
|
||||||
|
self.template_name,
|
||||||
|
{
|
||||||
|
"form": form,
|
||||||
|
"instance": instance,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response["HX-Trigger"] = self.hx_trigger
|
||||||
|
return response
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
self.template_name,
|
||||||
|
{
|
||||||
|
"form": form,
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.utils.text import gettext_lazy as _
|
||||||
|
from django.contrib.auth.forms import PasswordChangeForm
|
||||||
|
|
||||||
|
from web.mixins import StylingMixin
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class ChangePasswordForm(StylingMixin, PasswordChangeForm):
|
||||||
|
styled_fields = ("old_password", "new_password1", "new_password2")
|
||||||
|
placeholder_for_field = {
|
||||||
|
"old_password": _("********"),
|
||||||
|
"new_password1": _("********"),
|
||||||
|
"new_password2": _("********"),
|
||||||
|
}
|
||||||
+6
-1
@@ -15,7 +15,12 @@ class UserInfoForm(StylingMixin, forms.ModelForm):
|
|||||||
"last_name": _("Moreno"),
|
"last_name": _("Moreno"),
|
||||||
}
|
}
|
||||||
|
|
||||||
classes = StylingMixin.classes.difference({'w-full'})
|
def save(self, commit=True):
|
||||||
|
self.instance.username = self.cleaned_data.get("email")
|
||||||
|
return super().save(commit)
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
return super().is_valid()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|||||||
+1
-50
@@ -1,9 +1,5 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django import forms
|
|
||||||
from django.db import models
|
|
||||||
from django.views.generic import TemplateView
|
|
||||||
from django.shortcuts import get_object_or_404, render
|
|
||||||
|
|
||||||
|
|
||||||
class FilteredQuerysetMixin:
|
class FilteredQuerysetMixin:
|
||||||
@@ -62,11 +58,7 @@ class StylingMixin:
|
|||||||
for field_name in self.styled_fields:
|
for field_name in self.styled_fields:
|
||||||
field = self.fields[field_name]
|
field = self.fields[field_name]
|
||||||
field.required = True
|
field.required = True
|
||||||
field.widget.attrs.update(
|
field.widget.attrs.update({"class": " ".join(self.classes)})
|
||||||
{
|
|
||||||
"class": ' '.join(self.classes)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.placeholder_for_field.get(field_name):
|
if self.placeholder_for_field.get(field_name):
|
||||||
field.widget.attrs.update(
|
field.widget.attrs.update(
|
||||||
@@ -74,44 +66,3 @@ class StylingMixin:
|
|||||||
"placeholder": self.placeholder_for_field.get(field_name),
|
"placeholder": self.placeholder_for_field.get(field_name),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReadUpdateView(TemplateView):
|
|
||||||
model: models.Model = None
|
|
||||||
form_class: forms.ModelForm = None
|
|
||||||
template_name: str = ""
|
|
||||||
ok_template_name: str = ""
|
|
||||||
error_template_name: str = ""
|
|
||||||
|
|
||||||
def get_model(self):
|
|
||||||
return self.model
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
pk = self.kwargs.get('pk')
|
|
||||||
cls = self.get_model()
|
|
||||||
return get_object_or_404(cls, pk=pk)
|
|
||||||
|
|
||||||
def get_initial_values(self, instance):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
form = self.form_class(initial=self.get_initial_values(instance))
|
|
||||||
|
|
||||||
return {
|
|
||||||
"form": form,
|
|
||||||
}
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
form = self.form_class(request.POST)
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
instance = form.save()
|
|
||||||
return render(request, self.ok_template_name, {
|
|
||||||
"form": form,
|
|
||||||
"instance": instance,
|
|
||||||
})
|
|
||||||
|
|
||||||
return render(request, self.error_template_name, {
|
|
||||||
"form": form,
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
<form hx-post="{{ url }}" hx-swap="none">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="{{ field.id_for_label }}"
|
||||||
|
class="inline-block text-sm font-medium text-gray-900 dark:text-white ml-2">
|
||||||
|
{{ field.label }}
|
||||||
|
</label>
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for k, error in form.errors.items %}
|
||||||
|
<span class="flex justify-center mb-2 text-sm font-medium text-red-900 dark:text-red-400">{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<button class="text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none
|
||||||
|
focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600
|
||||||
|
dark:hover:bg-primary-700 dark:focus:ring-primary-800"
|
||||||
|
>
|
||||||
|
{% translate 'Guardar' %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<form hx-post="{% url 'web:user_info_component' %}" hx-swap="none">
|
<form hx-post="{{ url }}" hx-swap="none">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
|
||||||
<label for="{{ field.id_for_label }}"
|
<label for="{{ field.id_for_label }}"
|
||||||
class="inline-block text-sm font-medium text-gray-900 dark:text-white ml-2">
|
class="inline-block text-sm font-medium text-gray-900 dark:text-white ml-2">
|
||||||
{{ field.label }}
|
{{ field.label }}
|
||||||
@@ -23,6 +22,4 @@
|
|||||||
>
|
>
|
||||||
{% translate 'Guardar' %}
|
{% translate 'Guardar' %}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -159,14 +159,8 @@
|
|||||||
{% translate 'Editar datos' %}
|
{% translate 'Editar datos' %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% if not forloop.last %}
|
{% if not forloop.last %}
|
||||||
<hr class="h-px my-2 bg-gray-200 border-0 dark:bg-gray-700">
|
<hr class="h-px my-2 bg-gray-200 border-0 dark:bg-gray-700">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,14 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div
|
<div
|
||||||
class="mt-4 p-6 border border-gray-100 rounded-md dark:border-gray-600"
|
class="mt-4 p-6 border border-gray-100 rounded-md dark:border-gray-600"
|
||||||
hx-get="{% url 'web:user_info_component' %}" hx-trigger="load"
|
hx-get="{% url 'web:user_info_component' %}" hx-trigger="load, updated_user_info"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mt-4 p-6 border border-gray-100 rounded-md dark:border-gray-600"
|
||||||
|
hx-get="{% url 'web:change_password_component' %}" hx-trigger="load, changed_password"
|
||||||
>
|
>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from shop.tests.mixins import CreateProductsMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TestChangeUserPassword(TestCase, CreateProductsMixin):
|
||||||
|
def setUp(self):
|
||||||
|
self.password = "ihatesand"
|
||||||
|
self.user = User.objects.create_user("vader", "darth@vader.com", self.password)
|
||||||
|
|
||||||
|
def test_update_user_info(self):
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
response = self.client.get(reverse("web:change_password_component"))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
new_password = "ifnhatesandbro"
|
||||||
|
data = {
|
||||||
|
"old_password": self.password,
|
||||||
|
"new_password1": new_password,
|
||||||
|
"new_password2": new_password,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(reverse("web:change_password_component"), data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
self.user.refresh_from_db()
|
||||||
|
|
||||||
|
assert self.user.check_password(new_password)
|
||||||
@@ -11,7 +11,7 @@ class TestCustomerAddresses(TestCase, CreateProductsMixin):
|
|||||||
self.user = User.objects.create_user("vader", "darth@vader.com", "ihatesand")
|
self.user = User.objects.create_user("vader", "darth@vader.com", "ihatesand")
|
||||||
self.customer_address = CustomerAddress.objects.create(
|
self.customer_address = CustomerAddress.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
full_name='Darth Vader',
|
full_name="Darth Vader",
|
||||||
address_type=CustomerAddress.Types.SHIPPING,
|
address_type=CustomerAddress.Types.SHIPPING,
|
||||||
address="Vader Fortress",
|
address="Vader Fortress",
|
||||||
address_town="Mustafar",
|
address_town="Mustafar",
|
||||||
@@ -33,13 +33,18 @@ class TestCustomerAddresses(TestCase, CreateProductsMixin):
|
|||||||
"address_zip": "00001",
|
"address_zip": "00001",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse("web:update_customer_address", kwargs={"pk": self.customer_address.pk}), data)
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"web:update_customer_address", kwargs={"pk": self.customer_address.pk}
|
||||||
|
),
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.customer_address.refresh_from_db()
|
self.customer_address.refresh_from_db()
|
||||||
|
|
||||||
assert self.customer_address.full_name == data.get('full_name')
|
assert self.customer_address.full_name == data.get("full_name")
|
||||||
assert self.customer_address.address == data.get('address')
|
assert self.customer_address.address == data.get("address")
|
||||||
assert self.customer_address.address_town == data.get('address_town')
|
assert self.customer_address.address_town == data.get("address_town")
|
||||||
assert self.customer_address.address_state == data.get('address_state')
|
assert self.customer_address.address_state == data.get("address_state")
|
||||||
assert self.customer_address.address_phone == data.get('address_phone')
|
assert self.customer_address.address_phone == data.get("address_phone")
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.contrib.auth.models import User
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from shop.models import CustomerAddress
|
|
||||||
from shop.tests.mixins import CreateProductsMixin
|
from shop.tests.mixins import CreateProductsMixin
|
||||||
|
|
||||||
|
|
||||||
@@ -13,12 +12,13 @@ class TestUserInfo(TestCase, CreateProductsMixin):
|
|||||||
def test_update_user_info(self):
|
def test_update_user_info(self):
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
response = self.client.get(reverse("web:user_info_component"))
|
response = self.client.get(reverse("web:user_info_component"))
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"first_name": "Anakin",
|
"first_name": "Anakin",
|
||||||
"last_name": "Skywalker",
|
"last_name": "Skywalker",
|
||||||
"email": "anakin@skywalker.com"
|
"email": "anakin@skywalker.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse("web:user_info_component"), data)
|
response = self.client.post(reverse("web:user_info_component"), data)
|
||||||
@@ -26,6 +26,6 @@ class TestUserInfo(TestCase, CreateProductsMixin):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.user.refresh_from_db()
|
self.user.refresh_from_db()
|
||||||
|
|
||||||
assert self.user.first_name == data.get('first_name')
|
assert self.user.first_name == data.get("first_name")
|
||||||
assert self.user.last_name == data.get('last_name')
|
assert self.user.last_name == data.get("last_name")
|
||||||
assert self.user.email == data.get('email')
|
assert self.user.email == data.get("email")
|
||||||
|
|||||||
+6
-1
@@ -11,7 +11,7 @@ from web.views.components import (
|
|||||||
list_wishlisted_products,
|
list_wishlisted_products,
|
||||||
update_customer_address,
|
update_customer_address,
|
||||||
user_info_component,
|
user_info_component,
|
||||||
wishlist_button,
|
wishlist_button, change_password_component,
|
||||||
)
|
)
|
||||||
from web.views.users import (
|
from web.views.users import (
|
||||||
login,
|
login,
|
||||||
@@ -91,6 +91,11 @@ urlpatterns = [
|
|||||||
login_required(user_info_component, login_url=_("/login/")),
|
login_required(user_info_component, login_url=_("/login/")),
|
||||||
name="user_info_component",
|
name="user_info_component",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"web/components/change-password/",
|
||||||
|
login_required(change_password_component, login_url=_("/login/")),
|
||||||
|
name="change_password_component",
|
||||||
|
),
|
||||||
# api
|
# api
|
||||||
path("web/add-cart-item/", add_cart_item, name="add_cart_item"),
|
path("web/add-cart-item/", add_cart_item, name="add_cart_item"),
|
||||||
path("web/delete-cart-item/<int:pk>/", delete_cart_item, name="delete_cart_item"),
|
path("web/delete-cart-item/<int:pk>/", delete_cart_item, name="delete_cart_item"),
|
||||||
|
|||||||
+31
-25
@@ -1,9 +1,12 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.urls import reverse
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from config.mixins import HTMXFormComponent
|
||||||
from shop.filters import ProductFilter
|
from shop.filters import ProductFilter
|
||||||
from shop.models import (
|
from shop.models import (
|
||||||
CartItem,
|
CartItem,
|
||||||
@@ -13,10 +16,10 @@ from shop.models import (
|
|||||||
ShippingMethod,
|
ShippingMethod,
|
||||||
WishlistedProduct,
|
WishlistedProduct,
|
||||||
)
|
)
|
||||||
|
from users.forms.change_password import ChangePasswordForm
|
||||||
from users.forms.info import UserInfoForm
|
from users.forms.info import UserInfoForm
|
||||||
from web.forms import CustomerAddressForm
|
from web.forms import CustomerAddressForm
|
||||||
from web.mixins import FilteredQuerysetMixin, PaginatedQuerysetMixin
|
from web.mixins import FilteredQuerysetMixin, PaginatedQuerysetMixin
|
||||||
from web.models import WebSettings
|
|
||||||
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
|
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
|
||||||
from web.utils import get_or_create_cart
|
from web.utils import get_or_create_cart
|
||||||
|
|
||||||
@@ -146,37 +149,39 @@ class WishlistButton(TemplateView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserInfoComponentView(TemplateView):
|
class UserInfoFormComponentView(HTMXFormComponent):
|
||||||
template_name = "components/users/info.html"
|
|
||||||
form_class = UserInfoForm
|
form_class = UserInfoForm
|
||||||
|
hx_trigger = "updated_user_info"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_object(self) -> models.Model:
|
||||||
web_settings = WebSettings.load()
|
return self.request.user
|
||||||
user = self.request.user
|
|
||||||
form = self.form_class(
|
|
||||||
initial={
|
|
||||||
"email": user.email,
|
|
||||||
"first_name": user.first_name,
|
|
||||||
"last_name": user.last_name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def get_view_name(self):
|
||||||
|
return reverse("web:user_info_component")
|
||||||
|
|
||||||
|
def get_initial_values(self, instance):
|
||||||
return {
|
return {
|
||||||
"form": form,
|
"email": instance.email,
|
||||||
|
"first_name": instance.first_name,
|
||||||
|
"last_name": instance.last_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
user = self.request.user
|
|
||||||
form = self.form_class(request.POST)
|
|
||||||
|
|
||||||
if form.is_valid():
|
class ChangePasswordFormComponentView(HTMXFormComponent):
|
||||||
user.email = form.cleaned_data.get("email")
|
form_class = ChangePasswordForm
|
||||||
user.first_name = form.cleaned_data.get("first_name")
|
hx_trigger = "changed_password"
|
||||||
user.last_name = form.cleaned_data.get("last_name")
|
|
||||||
user.save()
|
|
||||||
return HttpResponse(status=200)
|
|
||||||
|
|
||||||
return render(request, self.template_name, {"form": form})
|
def get_form(self, initial=True):
|
||||||
|
if initial:
|
||||||
|
return self.form_class(user=self.request.user)
|
||||||
|
|
||||||
|
return self.form_class(self.request.user, self.request.POST)
|
||||||
|
|
||||||
|
def get_object(self) -> models.Model:
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
def get_view_name(self):
|
||||||
|
return reverse("web:change_password_component")
|
||||||
|
|
||||||
|
|
||||||
class ListCustomerAddressComponent(TemplateView):
|
class ListCustomerAddressComponent(TemplateView):
|
||||||
@@ -203,5 +208,6 @@ list_products = ListProducts.as_view()
|
|||||||
list_wishlisted_products = ListWishlistedProducts.as_view()
|
list_wishlisted_products = ListWishlistedProducts.as_view()
|
||||||
wishlist_button = WishlistButton.as_view()
|
wishlist_button = WishlistButton.as_view()
|
||||||
|
|
||||||
user_info_component = UserInfoComponentView.as_view()
|
user_info_component = UserInfoFormComponentView.as_view()
|
||||||
|
change_password_component = ChangePasswordFormComponentView.as_view()
|
||||||
list_customer_addresses_component = ListCustomerAddressComponent.as_view()
|
list_customer_addresses_component = ListCustomerAddressComponent.as_view()
|
||||||
|
|||||||
Reference in New Issue
Block a user