Merged in [17908] from rjsparks@nostrum.com:
Add used_roles to Group and default_used_roles to GroupFeatures. Dynamically build the group edit form to provide a field for all used_roles. Fixes #2785 and #2572. Partially addresses #2160.
- Legacy-Id: 17960
Note: SVN reference [17908] has been migrated to Git commit 2862727d02
This commit is contained in:
commit
567449d3d2
|
@ -11,17 +11,17 @@ import debug # pyflakes:ignore
|
|||
# Django imports
|
||||
from django import forms
|
||||
from django.utils.html import mark_safe # type:ignore
|
||||
from django.db.models import F
|
||||
|
||||
# IETF imports
|
||||
from ietf.group.models import Group, GroupHistory, GroupStateName
|
||||
from ietf.name.models import ReviewTypeName
|
||||
from ietf.group.models import Group, GroupHistory, GroupStateName, GroupFeatures
|
||||
from ietf.name.models import ReviewTypeName, RoleName
|
||||
from ietf.person.fields import SearchableEmailsField, PersonEmailChoiceField
|
||||
from ietf.person.models import Person
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.review.models import ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings
|
||||
from ietf.review.policies import get_reviewer_queue_policy
|
||||
from ietf.review.utils import close_review_request_states
|
||||
from ietf.utils.textupload import get_cleaned_text_file_content
|
||||
from ietf.utils.text import strip_suffix
|
||||
#from ietf.utils.ordereddict import insert_after_in_ordered_dict
|
||||
from ietf.utils.fields import DatepickerDateField, MultiEmailField
|
||||
|
||||
|
@ -29,14 +29,6 @@ from ietf.utils.fields import DatepickerDateField, MultiEmailField
|
|||
|
||||
MAX_GROUP_DELEGATES = 3
|
||||
|
||||
# --- Utility Functions ------------------------------------------------
|
||||
|
||||
def roles_for_group_type(group_type):
|
||||
roles = ["chair", "secr", "techadv", "delegate", ]
|
||||
if group_type == "review":
|
||||
roles.append("reviewer")
|
||||
return roles
|
||||
|
||||
# --- Forms ------------------------------------------------------------
|
||||
|
||||
class StatusUpdateForm(forms.Form):
|
||||
|
@ -65,14 +57,7 @@ class GroupForm(forms.Form):
|
|||
name = forms.CharField(max_length=80, label="Name", required=True)
|
||||
acronym = forms.CharField(max_length=40, label="Acronym", required=True)
|
||||
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
|
||||
|
||||
# roles
|
||||
chair_roles = SearchableEmailsField(label="Chairs", required=False, only_users=True)
|
||||
secr_roles = SearchableEmailsField(label="Secretaries", required=False, only_users=True)
|
||||
techadv_roles = SearchableEmailsField(label="Technical Advisors", required=False, only_users=True)
|
||||
delegate_roles = SearchableEmailsField(label="Delegates", required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
|
||||
help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - at most %s persons at a given time." % MAX_GROUP_DELEGATES))
|
||||
reviewer_roles = SearchableEmailsField(label="Reviewers", required=False, only_users=True)
|
||||
# Note that __init__ will add role fields here
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type='area').order_by('name'), label="Shepherding AD", empty_label="(None)", required=False)
|
||||
|
||||
parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False)
|
||||
|
@ -85,16 +70,39 @@ class GroupForm(forms.Form):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.group = kwargs.pop('group', None)
|
||||
self.group_type = kwargs.pop('group_type', False)
|
||||
if self.group:
|
||||
self.used_roles = self.group.used_roles or self.group.features.default_used_roles
|
||||
else:
|
||||
self.used_roles = GroupFeatures.objects.get(type=self.group_type).default_used_roles
|
||||
if "field" in kwargs:
|
||||
field = kwargs["field"]
|
||||
del kwargs["field"]
|
||||
if field in roles_for_group_type(self.group_type):
|
||||
if field in self.used_roles:
|
||||
field = field + "_roles"
|
||||
else:
|
||||
field = None
|
||||
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
for role_slug in self.used_roles:
|
||||
role_name = RoleName.objects.get(slug=role_slug)
|
||||
fieldname = '%s_roles'%role_slug
|
||||
field_args = {
|
||||
'label' : role_name.name,
|
||||
'required' : False,
|
||||
'only_users' : True,
|
||||
}
|
||||
if fieldname == 'delegate_roles':
|
||||
field_args['max_entries'] = MAX_GROUP_DELEGATES
|
||||
field_args['help_text'] = mark_safe("Chairs can delegate the authority to update the state of group documents - at most %s persons at a given time." % MAX_GROUP_DELEGATES)
|
||||
self.fields[fieldname] = SearchableEmailsField(**field_args)
|
||||
self.fields[fieldname].initial = Email.objects.filter(person__role__name_id=role_slug,person__role__group=self.group,person__role__email__pk=F('pk')).distinct()
|
||||
|
||||
self.adjusted_field_order = ['name','acronym','state']
|
||||
for role_slug in self.used_roles:
|
||||
self.adjusted_field_order.append('%s_roles'%role_slug)
|
||||
self.order_fields(self.adjusted_field_order)
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields["state"].queryset = self.fields["state"].queryset.exclude(slug__in=("bof", "bof-conc"))
|
||||
|
||||
|
@ -116,10 +124,6 @@ class GroupForm(forms.Form):
|
|||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type="area")
|
||||
self.fields['parent'].label = "IETF Area"
|
||||
|
||||
role_fields_to_remove = (set(strip_suffix(attr, "_roles") for attr in self.fields if attr.endswith("_roles"))
|
||||
- set(roles_for_group_type(self.group_type)))
|
||||
for r in role_fields_to_remove:
|
||||
del self.fields[r + "_roles"]
|
||||
if field:
|
||||
keys = list(self.fields.keys())
|
||||
for f in keys:
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 2.0.13 on 2020-05-22 12:00
|
||||
|
||||
from django.db import migrations
|
||||
import jsonfield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('group', '0028_add_robots'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='used_roles',
|
||||
field=jsonfield.fields.JSONField(default=[], help_text="Leave an empty list to get the group_type's default used roles", max_length=128),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='groupfeatures',
|
||||
name='default_used_roles',
|
||||
field=jsonfield.fields.JSONField(default=[], max_length=128),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='grouphistory',
|
||||
name='used_roles',
|
||||
field=jsonfield.fields.JSONField(default=[], help_text="Leave an empty list to get the group_type's default used roles", max_length=128),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalgroupfeatures',
|
||||
name='default_used_roles',
|
||||
field=jsonfield.fields.JSONField(default=[], max_length=128),
|
||||
),
|
||||
]
|
46
ietf/group/migrations/0030_populate_default_used_roles.py
Normal file
46
ietf/group/migrations/0030_populate_default_used_roles.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Generated by Django 2.0.13 on 2020-05-22 11:41
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
grouptype_defaults = {
|
||||
'adhoc': ['matman', 'ad', 'chair', 'lead'],
|
||||
'admin': ['member', 'chair'],
|
||||
'ag': ['ad', 'chair', 'secr'],
|
||||
'area': ['ad'],
|
||||
'dir': ['ad', 'chair', 'reviewer', 'secr'],
|
||||
'review': ['ad', 'chair', 'reviewer', 'secr'],
|
||||
'iab': ['chair'],
|
||||
'iana': ['auth'],
|
||||
'iesg': [],
|
||||
'ietf': ['ad', 'member', 'comdir', 'delegate', 'execdir', 'recman', 'secr', 'trac-editor', 'trac-admin', 'chair'],
|
||||
'individ': ['ad'],
|
||||
'irtf': ['member', 'atlarge', 'chair'],
|
||||
'ise': ['chair'],
|
||||
'isoc': ['chair', 'ceo'],
|
||||
'nomcom': ['member', 'advisor', 'liaison', 'chair', 'techadv'],
|
||||
'program': ['member', 'chair', 'lead'],
|
||||
'rfcedtyp': ['auth', 'chair'],
|
||||
'rg': ['chair', 'techadv', 'secr', 'delegate'],
|
||||
'sdo': ['liaiman', 'ceo', 'coord', 'auth', 'chair'],
|
||||
'team': ['ad', 'member', 'delegate', 'secr', 'liaison', 'atlarge', 'chair', 'matman', 'techadv'],
|
||||
'wg': ['ad', 'editor', 'delegate', 'secr', 'chair', 'matman', 'techadv'],
|
||||
}
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
GroupFeatures = apps.get_model('group','GroupFeatures')
|
||||
for type_id, roles in grouptype_defaults.items():
|
||||
GroupFeatures.objects.filter(type_id=type_id).update(default_used_roles=roles)
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
pass # intentional
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('group', '0029_add_used_roles_and_default_used_roles'),
|
||||
('stats', '0003_meetingregistration_attended'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse),
|
||||
]
|
|
@ -43,6 +43,8 @@ class GroupInfo(models.Model):
|
|||
unused_states = models.ManyToManyField('doc.State', help_text="Document states that have been disabled for the group.", blank=True)
|
||||
unused_tags = models.ManyToManyField(DocTagName, help_text="Document tags that have been disabled for the group.", blank=True)
|
||||
|
||||
used_roles = jsonfield.JSONField(max_length=128, blank=False, default=[], help_text="Leave an empty list to get the group_type's default used roles")
|
||||
|
||||
uses_milestone_dates = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -232,6 +234,7 @@ class GroupFeatures(models.Model):
|
|||
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=128, 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",])
|
||||
|
|
|
@ -567,6 +567,8 @@ class GroupEditTests(TestCase):
|
|||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form select[name=parent]')), 1)
|
||||
self.assertEqual(len(q('form input[name=acronym]')), 1)
|
||||
for role_slug in group.used_roles or group.features.default_used_roles:
|
||||
self.assertEqual(len(q('form input[name=%s_roles]'%role_slug)),1)
|
||||
|
||||
# faulty post
|
||||
Group.objects.create(name="Collision Test Group", acronym="collide")
|
||||
|
|
|
@ -128,14 +128,6 @@ from ietf.utils.text import strip_suffix
|
|||
def roles(group, role_name):
|
||||
return Role.objects.filter(group=group, name=role_name).select_related("email", "person")
|
||||
|
||||
def roles_for_group_type(group_type):
|
||||
roles = ["chair", "secr", "techadv", "delegate", ]
|
||||
if group_type == "review":
|
||||
roles.append("reviewer")
|
||||
return roles
|
||||
|
||||
|
||||
|
||||
|
||||
def fill_in_charter_info(group, include_drafts=False):
|
||||
group.areadirector = getattr(group.ad_role(),'email',None)
|
||||
|
@ -546,7 +538,7 @@ def group_about(request, acronym, group_type=None):
|
|||
"can_provide_status_update": can_provide_update,
|
||||
"status_update": status_update,
|
||||
"charter_submit_url": charter_submit_url,
|
||||
"editable_roles": roles_for_group_type(group_type),
|
||||
"editable_roles": group.used_roles or group.features.default_used_roles,
|
||||
"closing_note": e,
|
||||
}))
|
||||
|
||||
|
@ -1076,8 +1068,6 @@ def edit(request, group_type=None, acronym=None, action="edit", field=None):
|
|||
closing_note = closing_note,
|
||||
)
|
||||
|
||||
for slug in roles_for_group_type(group_type):
|
||||
init[slug + "_roles"] = Email.objects.filter(role__group=group, role__name=slug).order_by('role__person__name')
|
||||
else:
|
||||
init = dict(ad=request.user.person.id if group_type == "wg" and has_role(request.user, "Area Director") else None,
|
||||
)
|
||||
|
|
|
@ -2483,6 +2483,7 @@
|
|||
"custom_group_roles": false,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"matman\",\"ad\",\"chair\",\"lead\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\",\"lead\",\"delegate\"]",
|
||||
|
@ -2514,6 +2515,7 @@
|
|||
"custom_group_roles": false,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"member\",\"chair\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\"]",
|
||||
|
@ -2545,6 +2547,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"ad\",\"chair\",\"secr\"]",
|
||||
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
|
||||
"groupman_authroles": "[\"Secretariat\",\"Area Director\"]",
|
||||
"groupman_roles": "[\"ad\",\"chair\",\"delegate\"]",
|
||||
|
@ -2576,6 +2579,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"ad\"]",
|
||||
"docman_roles": "[\"ad\",\"delegate\",\"secr\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"ad\"]",
|
||||
|
@ -2607,6 +2611,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"ad\",\"chair\",\"reviewer\",\"secr\"]",
|
||||
"docman_roles": "[]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"ad\",\"secr\"]",
|
||||
|
@ -2638,6 +2643,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"chair\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[]",
|
||||
|
@ -2669,6 +2675,7 @@
|
|||
"custom_group_roles": false,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"auth\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\"]",
|
||||
|
@ -2700,6 +2707,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\",\"delegate\"]",
|
||||
|
@ -2731,6 +2739,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"ad\",\"member\",\"comdir\",\"delegate\",\"execdir\",\"recman\",\"secr\",\"trac-editor\",\"trac-admin\",\"chair\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\",\"delegate\"]",
|
||||
|
@ -2762,6 +2771,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"ad\"]",
|
||||
"docman_roles": "[\"auth\"]",
|
||||
"groupman_authroles": "[]",
|
||||
"groupman_roles": "[]",
|
||||
|
@ -2793,6 +2803,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"member\",\"atlarge\",\"chair\"]",
|
||||
"docman_roles": "[]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\",\"delegate\"]",
|
||||
|
@ -2824,6 +2835,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"chair\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\",\"delegate\"]",
|
||||
|
@ -2855,6 +2867,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"chair\",\"ceo\"]",
|
||||
"docman_roles": "[]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\"]",
|
||||
|
@ -2886,6 +2899,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"member\",\"advisor\",\"liaison\",\"chair\",\"techadv\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\",\"advisor\"]",
|
||||
|
@ -2917,6 +2931,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"member\",\"chair\",\"lead\"]",
|
||||
"docman_roles": "[\"lead\",\"chair\",\"secr\"]",
|
||||
"groupman_authroles": "[\"Secretariat\",\"IAB\"]",
|
||||
"groupman_roles": "[\"lead\",\"chair\",\"secr\"]",
|
||||
|
@ -2948,6 +2963,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.review_requests",
|
||||
"default_used_roles": "[\"ad\",\"chair\",\"reviewer\",\"secr\"]",
|
||||
"docman_roles": "[\"secr\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"ad\",\"secr\"]",
|
||||
|
@ -2979,6 +2995,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"auth\",\"chair\"]",
|
||||
"docman_roles": "[]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[]",
|
||||
|
@ -3010,6 +3027,7 @@
|
|||
"custom_group_roles": false,
|
||||
"customize_workflow": true,
|
||||
"default_tab": "ietf.group.views.group_documents",
|
||||
"default_used_roles": "[\"chair\",\"techadv\",\"secr\",\"delegate\"]",
|
||||
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
|
||||
"groupman_authroles": "[\"Secretariat\",\"IRTF Chair\"]",
|
||||
"groupman_roles": "[\"chair\",\"delegate\"]",
|
||||
|
@ -3041,6 +3059,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"liaiman\",\"ceo\",\"coord\",\"auth\",\"chair\"]",
|
||||
"docman_roles": "[\"liaiman\",\"matman\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[]",
|
||||
|
@ -3072,6 +3091,7 @@
|
|||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\"ad\",\"member\",\"delegate\",\"secr\",\"liaison\",\"atlarge\",\"chair\",\"matman\",\"techadv\"]",
|
||||
"docman_roles": "[\"chair\"]",
|
||||
"groupman_authroles": "[\"Secretariat\"]",
|
||||
"groupman_roles": "[\"chair\"]",
|
||||
|
@ -3103,6 +3123,7 @@
|
|||
"custom_group_roles": false,
|
||||
"customize_workflow": true,
|
||||
"default_tab": "ietf.group.views.group_documents",
|
||||
"default_used_roles": "[\"ad\",\"editor\",\"delegate\",\"secr\",\"chair\",\"matman\",\"techadv\"]",
|
||||
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
|
||||
"groupman_authroles": "[\"Secretariat\",\"Area Director\"]",
|
||||
"groupman_roles": "[\"ad\",\"chair\",\"delegate\",\"secr\"]",
|
||||
|
|
|
@ -19,6 +19,7 @@ class Migration(migrations.Migration):
|
|||
|
||||
dependencies = [
|
||||
('stats', '0002_add_meetingregistration_fields'),
|
||||
('group', '0029_add_used_roles_and_default_used_roles'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
Loading…
Reference in a new issue