Skip to content

Commit

Permalink
Add a new Django field, IETFJSONField
Browse files Browse the repository at this point in the history
This field is needed because the plain JSONField does not permit empty arrays - [] - or empty objects - {} - when the field is marked as required.  Those values explicitly evaluate to a null value, and are rejected.

Instead, the IETFJSONField accepts two new arguments to control this:
- empty_values: An array of values that should evaluate to null/empty, and be rejected.
- accepted_empty_values: An array of values that should *not* evaluate to null/empty, and be accepted.

This allows the programmer to specify either a positive or negative statement of what values to accept.

Fixes issue #3331.  Commit ready for merge.
 - Legacy-Id: 19401
  • Loading branch information
meadmaker committed Oct 7, 2021
1 parent 58fa321 commit 604d6ed
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 8 deletions.
17 changes: 9 additions & 8 deletions ietf/group/models.py
Expand Up @@ -23,6 +23,7 @@
from ietf.group.colors import fg_group_colors, bg_group_colors
from ietf.name.models import GroupStateName, GroupTypeName, DocTagName, GroupMilestoneStateName, RoleName, AgendaTypeName, ExtResourceName
from ietf.person.models import Email, Person
from ietf.utils.db import IETFJSONField
from ietf.utils.mail import formataddr, send_mail_text
from ietf.utils import log
from ietf.utils.models import ForeignKey, OneToOneField
Expand Down Expand Up @@ -282,14 +283,14 @@ class GroupFeatures(models.Model):
agenda_type = models.ForeignKey(AgendaTypeName, null=True, default="ietf", on_delete=CASCADE)
about_page = models.CharField(max_length=64, blank=False, default="ietf.group.views.group_about" )
default_tab = models.CharField(max_length=64, blank=False, default="ietf.group.views.group_about" )
material_types = jsonfield.JSONField(max_length=64, blank=False, default=["slides"])
default_used_roles = jsonfield.JSONField(max_length=256, blank=False, default=[])
admin_roles = jsonfield.JSONField(max_length=64, blank=False, default=["chair"]) # Trac Admin
docman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair","delegate","secr"])
groupman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair",])
groupman_authroles = jsonfield.JSONField(max_length=128, blank=False, default=["Secretariat",])
matman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair","delegate","secr"])
role_order = jsonfield.JSONField(max_length=128, blank=False, default=["chair","secr","member"],
material_types = IETFJSONField(max_length=64, accepted_empty_values=[[], {}], blank=False, default=["slides"])
default_used_roles = IETFJSONField(max_length=256, accepted_empty_values=[[], {}], blank=False, default=[])
admin_roles = IETFJSONField(max_length=64, accepted_empty_values=[[], {}], blank=False, default=["chair"]) # Trac Admin
docman_roles = IETFJSONField(max_length=128, accepted_empty_values=[[], {}], blank=False, default=["ad","chair","delegate","secr"])
groupman_roles = IETFJSONField(max_length=128, accepted_empty_values=[[], {}], blank=False, default=["ad","chair",])
groupman_authroles = IETFJSONField(max_length=128, accepted_empty_values=[[], {}], blank=False, default=["Secretariat",])
matman_roles = IETFJSONField(max_length=128, accepted_empty_values=[[], {}], blank=False, default=["ad","chair","delegate","secr"])
role_order = IETFJSONField(max_length=128, accepted_empty_values=[[], {}], blank=False, default=["chair","secr","member"],
help_text="The order in which roles are shown, for instance on photo pages. Enter valid JSON.")


Expand Down
28 changes: 28 additions & 0 deletions ietf/utils/db.py
@@ -0,0 +1,28 @@
# Copyright The IETF Trust 2021, All Rights Reserved
# -*- coding: utf-8 -*-

# Taken from/inspired by
# https://stackoverflow.com/questions/55147169/django-admin-jsonfield-default-empty-dict-wont-save-in-admin
#
# JSONField should recognize {}, (), and [] as valid, non-empty JSON
# values. However, the base Field class excludes them
import jsonfield

from ietf.utils.fields import IETFJSONField as FormIETFJSONField


class IETFJSONField(jsonfield.JSONField):
form_class = FormIETFJSONField

def __init__(self, *args, empty_values=FormIETFJSONField.empty_values, accepted_empty_values=None, **kwargs):
if accepted_empty_values is None:
accepted_empty_values = []
self.empty_values = [x
for x in empty_values
if x not in accepted_empty_values]
super().__init__(*args, **kwargs)

def formfield(self, **kwargs):
if issubclass(kwargs['form_class'], FormIETFJSONField):
kwargs.setdefault('empty_values', self.empty_values)
return super().formfield(**{**kwargs})
15 changes: 15 additions & 0 deletions ietf/utils/fields.py
Expand Up @@ -6,6 +6,8 @@
import json
import re

import jsonfield

import debug # pyflakes:ignore

from typing import Optional, Type # pyflakes:ignore
Expand Down Expand Up @@ -265,6 +267,19 @@ def clean(self, value):

return objs.first() if self.max_entries == 1 else objs


class IETFJSONField(jsonfield.fields.forms.JSONField):
def __init__(self, *args, empty_values=jsonfield.fields.forms.JSONField.empty_values,
accepted_empty_values=None, **kwargs):
if accepted_empty_values is None:
accepted_empty_values = []
self.empty_values = [x
for x in empty_values
if x not in accepted_empty_values]

super().__init__(*args, **kwargs)


class MissingOkImageField(models.ImageField):
"""Image field that can validate successfully if file goes missing
Expand Down

0 comments on commit 604d6ed

Please sign in to comment.