Django JSONField values aren't returned as a correct JSON-format in template when extracted using javascript
I have a model like
class UserGroup(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, unique=False, related_name="group_owner")
name = models.CharField(max_length=128)
group_users = models.JSONField(models.EmailField(), default=list, blank=True)
def get_absolute_url(self):
return reverse("user_group_instance", kwargs={"pk": self.pk})
now, in my in another app I filter for a list of UserGroup instances for the given user, and I parse that query-set to a ModelMultipleChoiceField.
That works fine and such, the issue is that I want to extract the group_users in the template and add them to an array, using java-script, thus I have create a div which stores the (I would assume) json-array in the data-members attribute
<div id="group-modal">
{% for group in form.groups.field.queryset %}
<label>
<input type="checkbox" class="group-checkbox" value="{{ group.id }}" data-members="{{ group.group_users|safe }}">
{{ group.name }}
</label>
<br>
{% endfor %}
</div>
now, when I in javascript get the data from the data-members the returned string is not a json-object e.g it is "['hello@world', 'foo@bar']".
The javascript is (simplified)
document.getElementById("save-groups").addEventListener("click", function() {
let selectedPeople = [];
document.querySelectorAll(".group-checkbox:checked").forEach(checkbox => {
let members = JSON.parse(checkbox.getAttribute("data-members"));
selectedPeople = [...new Set([...selectedPeople, ...members])];
});
and the JSON.parse fails.
I simply cannot wrap my head around why; I don't do any manually serialization of the json-data thus I let django do that stuff.
I could work around it by do some regex replacement, but I rather figure out what the issue is
I'm not sure that you need to pass the EmailField as the encoder for the JSONField. If you want to ensure that the value from that field ends up as JSON in the template rather than a python dict you can create a custom decoder:
import json
class CustomDecoder(json.JSONDecoder):
def decode(self, obj):
return json.dumps(super().decode(obj))
class Order(models.Model):
sku_id = models.CharField(max_length=10)
group_users = models.JSONField(decoder=CustomDecoder, blank=True)
Then saving {"email_address": "foo@bar.com"}" will end up as JSON in your template that can be passed to JSON.parse()
I'm not sure but I think your problem is due to the way Django's template engine renders the JSONField data. When you use {{ group.group_users|safe }}, Django converts the JSON data to a string representation, but it doesn't ensure that the string is in a valid JSON format that JavaScript can parse directly.
In case of you, the group_users field is being rendered as a python list string (e.g., "['hello@world', 'foo@bar']"), which is not valid json. json requires double quotes for strings, and the entire structure should be enclosed in double quotes. to solve, you need to ensure that the data is rendered as a valid json string in the template. You can achieve this by using Django's json_script template filter, which safely outputs a Python object as a JSON-encoded script.
I think you can modify your template in this way:
<div id="group-modal">
{% for group in form.groups.field.queryset %}
<label>
<input type="checkbox" class="group-checkbox" value="{{ group.id }}" data-members="{{ group.group_users|json_script:'group-members' }}">
{{ group.name }}
</label>
<br>
{% endfor %}
</div>
json_script filter will output a tag with the json data, which you can then access in your JavaScript and alternatively, if you want to keep using the data-members attribute, you can manually serialize the json data in the template:
<div id="group-modal">
{% for group in form.groups.field.queryset %}
<label>
<input type="checkbox" class="group-checkbox" value="{{ group.id }}" data-members="{{ group.group_users|json_script:'group-members' }}">
{{ group.name }}
</label>
<br>
{% endfor %}
And then in your javascript, you can parse the json data correctly:
document.getElementById("save-groups").addEventListener("click", function() {
let selectedPeople = [];
document.querySelectorAll(".group-checkbox:checked").forEach(checkbox => {
let members = JSON.parse(checkbox.getAttribute("data-members"));
selectedPeople = [...new Set([...selectedPeople, ...members])];
});
console.log(selectedPeople);});
By using the json_script filter, you ensure that the json data is correctly formatted and you can be parsed by Javascript without any issues. This approach also helps to prevent potential security issues related to XSS attacks by properly escaping the json data.