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(
|
||||
instance: User, created: bool, **kwargs: dict[Any, Any]
|
||||
) -> None:
|
||||
try:
|
||||
if type(instance.attributes) is not dict:
|
||||
instance.attributes = {}
|
||||
instance.save()
|
||||
if not created:
|
||||
return
|
||||
|
||||
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(
|
||||
instance.attributes.get("referrer", "")
|
||||
attrs.get("referrer", "")
|
||||
).decode()
|
||||
referrer = User.objects.get(uuid=referrer_uuid)
|
||||
code = f"WELCOME-{get_random_string(6)}"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,5 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
{% load i18n admin_modify %}
|
||||
|
||||
{% block submit_buttons_bottom %}
|
||||
{% submit_row %}
|
||||
|
||||
{% 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 %}
|
||||
{% include "admin/core/submit_line_with_storefront_link.html" %}
|
||||
{% 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,19 +153,25 @@ class UpdateUser(Mutation):
|
|||
user.set_password(password)
|
||||
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):
|
||||
user.attributes = {}
|
||||
for attribute_pair in attribute_pairs.split(";"):
|
||||
if "-" in attribute_pair:
|
||||
attr, value = attribute_pair.split("-", 1)
|
||||
user.attributes[attr] = value
|
||||
else:
|
||||
raise BadRequest(
|
||||
_(f"Invalid attribute format: {attribute_pair}")
|
||||
)
|
||||
|
||||
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:
|
||||
attr, value = attribute_pair.split("-", 1)
|
||||
user.attributes[attr] = value
|
||||
else:
|
||||
raise BadRequest(
|
||||
_(f"Invalid attribute format: {attribute_pair}")
|
||||
)
|
||||
else:
|
||||
raise BadRequest(_("attributes must be a dict or a string"))
|
||||
|
||||
for attr, value in kwargs.items():
|
||||
if attr == "password" or attr == "confirm_password":
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import orjson
|
||||
from cryptography.fernet import InvalidToken
|
||||
from django import forms
|
||||
from encrypted_fields.fields import EncryptedTextField
|
||||
|
||||
|
||||
|
|
@ -16,6 +17,9 @@ class EncryptedJSONTextField(EncryptedTextField):
|
|||
def get_internal_type(self) -> str:
|
||||
return "TextField"
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
return super().formfield(**{"form_class": forms.JSONField, **kwargs})
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if value is not None and not isinstance(value, str):
|
||||
value = orjson.dumps(value, default=str).decode("utf-8")
|
||||
|
|
|
|||
Loading…
Reference in a new issue