feat(admin): add reusable submit row with storefront link component
refactor submit row logic into a dedicated reusable template (`submit_line_with_storefront_link.html`) to improve maintainability and reduce duplication across admin templates. - Updated `change_form_with_storefront_link.html` to use the new template. - Added missing `formfield` implementation for `EncryptedJSONField` to ensure proper form compatibility. - Enhanced attributes handling in user-related mutation and signal functions for better flexibility and error handling.
This commit is contained in:
parent
af69abf8e3
commit
7e9c0f6dc2
5 changed files with 107 additions and 30 deletions
|
|
@ -70,14 +70,15 @@ def create_wishlist_on_user_creation_signal(
|
||||||
def create_promocode_on_user_referring(
|
def create_promocode_on_user_referring(
|
||||||
instance: User, created: bool, **kwargs: dict[Any, Any]
|
instance: User, created: bool, **kwargs: dict[Any, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
if not created:
|
||||||
if type(instance.attributes) is not dict:
|
return
|
||||||
instance.attributes = {}
|
|
||||||
instance.save()
|
|
||||||
|
|
||||||
if created and instance.attributes.get("referrer", ""):
|
try:
|
||||||
|
attrs = instance.attributes if isinstance(instance.attributes, dict) else {}
|
||||||
|
|
||||||
|
if attrs.get("referrer", ""):
|
||||||
referrer_uuid = urlsafe_base64_decode(
|
referrer_uuid = urlsafe_base64_decode(
|
||||||
instance.attributes.get("referrer", "")
|
attrs.get("referrer", "")
|
||||||
).decode()
|
).decode()
|
||||||
referrer = User.objects.get(uuid=referrer_uuid)
|
referrer = User.objects.get(uuid=referrer_uuid)
|
||||||
code = f"WELCOME-{get_random_string(6)}"
|
code = f"WELCOME-{get_random_string(6)}"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,5 @@
|
||||||
{% extends "admin/change_form.html" %}
|
{% extends "admin/change_form.html" %}
|
||||||
{% load i18n admin_modify %}
|
|
||||||
|
|
||||||
{% block submit_buttons_bottom %}
|
{% block submit_buttons_bottom %}
|
||||||
{% submit_row %}
|
{% include "admin/core/submit_line_with_storefront_link.html" %}
|
||||||
|
|
||||||
{% if original and storefront_url %}
|
|
||||||
<div class="flex justify-end px-4 -mt-2 pb-2">
|
|
||||||
<a href="{{ storefront_url }}"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
class="font-medium inline-flex group items-center gap-2 px-3 py-2 rounded-default justify-center whitespace-nowrap cursor-pointer border border-base-200 bg-white shadow-xs text-important dark:border-base-700 dark:bg-transparent hover:bg-base-100/80 dark:hover:bg-base-800/80 w-full lg:w-auto">
|
|
||||||
<span class="material-symbols-outlined text-base">open_in_new</span>
|
|
||||||
{% trans "See on site" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{% load i18n admin_urls unfold %}
|
||||||
|
|
||||||
|
<div {% if not is_popup %}id="submit-row"{% endif %} class="relative lg:sticky lg:bottom-0 z-40">
|
||||||
|
<div class="backdrop-blur-xs bg-white/80 rounded-b-default pb-4 px-4 dark:bg-base-900/80 lg:border-t lg:border-base-200 relative lg:scrollable-top lg:py-0 dark:border-base-800">
|
||||||
|
<div class="flex flex-col-reverse gap-3 items-center mx-auto lg:flex-row-reverse container lg:h-[64px]">
|
||||||
|
{% block submit-row %}
|
||||||
|
{% if show_save %}
|
||||||
|
{% component "unfold/components/button.html" with submit=1 form=opts.model_name|add:"_form" name="_save" class="w-full lg:w-auto" %}
|
||||||
|
{% trans "Save" %}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for action in actions_submit_line %}
|
||||||
|
{% component "unfold/components/button.html" with submit=1 form=opts.model_name|add:"_form" name=action.attrs.name|default:action.action_name variant="default" class="w-full lg:w-auto" attrs=action.attrs %}
|
||||||
|
{% if action.icon %}
|
||||||
|
<span class="material-symbols-outlined">
|
||||||
|
{{ action.icon }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ action.description }}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if show_save_and_continue %}
|
||||||
|
{% component "unfold/components/button.html" with submit=1 form=opts.model_name|add:"_form" name="_continue" variant="default" class="w-full lg:w-auto" %}
|
||||||
|
{% if can_change %}
|
||||||
|
{% trans "Save and continue editing" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Save and view" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if show_save_and_add_another %}
|
||||||
|
{% component "unfold/components/button.html" with submit=1 form=opts.model_name|add:"_form" name="_addanother" variant="default" class="w-full lg:w-auto" %}
|
||||||
|
{% trans "Save and add another" %}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if show_save_as_new %}
|
||||||
|
{% component "unfold/components/button.html" with submit=1 form=opts.model_name|add:"_form" name="_saveasnew" variant="default" class="w-full lg:w-auto" %}
|
||||||
|
{% trans "Save as new" %}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if original and storefront_url %}
|
||||||
|
<a href="{{ storefront_url }}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="font-medium inline-flex group items-center gap-2 px-3 py-2 rounded-default justify-center whitespace-nowrap cursor-pointer border border-base-200 bg-white shadow-xs text-important dark:border-base-700 dark:bg-transparent hover:bg-base-100/80 dark:hover:bg-base-800/80 w-full lg:w-auto">
|
||||||
|
<span class="material-symbols-outlined text-base">open_in_new</span>
|
||||||
|
{% trans "See on site" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3 mr-auto w-full lg:flex-row lg:w-auto">
|
||||||
|
{% if show_close or adminform.model_admin.change_form_show_cancel_button %}
|
||||||
|
{% url opts|admin_urlname:'changelist' as changelist_url %}
|
||||||
|
{% add_preserved_filters changelist_url as link %}
|
||||||
|
|
||||||
|
{% component "unfold/components/button.html" with href=link variant="default" class="w-full lg:w-auto"%}
|
||||||
|
{% trans "Close" %}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if show_delete_link and original %}
|
||||||
|
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
|
||||||
|
{% add_preserved_filters delete_url as link %}
|
||||||
|
|
||||||
|
{% component "unfold/components/button.html" with href=delete_url variant="danger" class="mr-auto w-full lg:w-auto" %}
|
||||||
|
{% trans "Delete" %} {{ opts.verbose_name }}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -153,12 +153,16 @@ class UpdateUser(Mutation):
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
attribute_pairs = kwargs.pop("attributes", "")
|
new_attributes = kwargs.pop("attributes", None)
|
||||||
|
|
||||||
if attribute_pairs:
|
if new_attributes is not None:
|
||||||
if not isinstance(user.attributes, dict):
|
if not isinstance(user.attributes, dict):
|
||||||
user.attributes = {}
|
user.attributes = {}
|
||||||
for attribute_pair in attribute_pairs.split(";"):
|
|
||||||
|
if isinstance(new_attributes, dict):
|
||||||
|
user.attributes.update(new_attributes)
|
||||||
|
elif isinstance(new_attributes, str) and new_attributes:
|
||||||
|
for attribute_pair in new_attributes.split(";"):
|
||||||
if "-" in attribute_pair:
|
if "-" in attribute_pair:
|
||||||
attr, value = attribute_pair.split("-", 1)
|
attr, value = attribute_pair.split("-", 1)
|
||||||
user.attributes[attr] = value
|
user.attributes[attr] = value
|
||||||
|
|
@ -166,6 +170,8 @@ class UpdateUser(Mutation):
|
||||||
raise BadRequest(
|
raise BadRequest(
|
||||||
_(f"Invalid attribute format: {attribute_pair}")
|
_(f"Invalid attribute format: {attribute_pair}")
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
raise BadRequest(_("attributes must be a dict or a string"))
|
||||||
|
|
||||||
for attr, value in kwargs.items():
|
for attr, value in kwargs.items():
|
||||||
if attr == "password" or attr == "confirm_password":
|
if attr == "password" or attr == "confirm_password":
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import orjson
|
import orjson
|
||||||
from cryptography.fernet import InvalidToken
|
from cryptography.fernet import InvalidToken
|
||||||
|
from django import forms
|
||||||
from encrypted_fields.fields import EncryptedTextField
|
from encrypted_fields.fields import EncryptedTextField
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,6 +17,9 @@ class EncryptedJSONTextField(EncryptedTextField):
|
||||||
def get_internal_type(self) -> str:
|
def get_internal_type(self) -> str:
|
||||||
return "TextField"
|
return "TextField"
|
||||||
|
|
||||||
|
def formfield(self, **kwargs):
|
||||||
|
return super().formfield(**{"form_class": forms.JSONField, **kwargs})
|
||||||
|
|
||||||
def get_prep_value(self, value):
|
def get_prep_value(self, value):
|
||||||
if value is not None and not isinstance(value, str):
|
if value is not None and not isinstance(value, str):
|
||||||
value = orjson.dumps(value, default=str).decode("utf-8")
|
value = orjson.dumps(value, default=str).decode("utf-8")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue