Merged in [19401] from mark@painless-security.com:

Add a new Django field, IETFJSONField
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.
 - Legacy-Id: 19440
Note: SVN reference [19401] has been migrated to Git commit 604d6edef0
This commit is contained in:
Robert Sparks 2021-10-19 18:42:09 +00:00
commit c5966052fd
4 changed files with 106 additions and 8 deletions

View file

@ -0,0 +1,54 @@
# Generated by Django 2.2.24 on 2021-10-19 11:36
from django.db import migrations
import ietf.utils.db
class Migration(migrations.Migration):
dependencies = [
('group', '0048_has_session_materials'),
]
operations = [
migrations.AlterField(
model_name='groupfeatures',
name='admin_roles',
field=ietf.utils.db.IETFJSONField(default=['chair'], max_length=64),
),
migrations.AlterField(
model_name='groupfeatures',
name='default_used_roles',
field=ietf.utils.db.IETFJSONField(default=[], max_length=256),
),
migrations.AlterField(
model_name='groupfeatures',
name='docman_roles',
field=ietf.utils.db.IETFJSONField(default=['ad', 'chair', 'delegate', 'secr'], max_length=128),
),
migrations.AlterField(
model_name='groupfeatures',
name='groupman_authroles',
field=ietf.utils.db.IETFJSONField(default=['Secretariat'], max_length=128),
),
migrations.AlterField(
model_name='groupfeatures',
name='groupman_roles',
field=ietf.utils.db.IETFJSONField(default=['ad', 'chair'], max_length=128),
),
migrations.AlterField(
model_name='groupfeatures',
name='material_types',
field=ietf.utils.db.IETFJSONField(default=['slides'], max_length=64),
),
migrations.AlterField(
model_name='groupfeatures',
name='matman_roles',
field=ietf.utils.db.IETFJSONField(default=['ad', 'chair', 'delegate', 'secr'], max_length=128),
),
migrations.AlterField(
model_name='groupfeatures',
name='role_order',
field=ietf.utils.db.IETFJSONField(default=['chair', 'secr', 'member'], help_text='The order in which roles are shown, for instance on photo pages. Enter valid JSON.', max_length=128),
),
]

View file

@ -23,6 +23,7 @@ import debug # pyflakes:ignore
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
@ -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.")

28
ietf/utils/db.py Normal file
View file

@ -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})

View file

@ -6,6 +6,8 @@ import datetime
import json
import re
import jsonfield
import debug # pyflakes:ignore
from typing import Optional, Type # pyflakes:ignore
@ -265,6 +267,19 @@ class SearchableField(forms.CharField):
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