2017-05-24 11:33:11 -04:00
|
|
|
from __future__ import unicode_literals
|
2017-11-07 11:08:23 -05:00
|
|
|
|
2016-08-23 16:45:26 -04:00
|
|
|
from collections import OrderedDict
|
|
|
|
|
2016-08-12 17:20:01 -04:00
|
|
|
from django import forms
|
2018-06-20 13:52:54 -04:00
|
|
|
from django.contrib.auth.models import User
|
2016-08-12 17:20:01 -04:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
2018-06-29 16:01:28 -04:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
2018-06-27 16:02:34 -04:00
|
|
|
from mptt.forms import TreeNodeMultipleChoiceField
|
2018-07-10 10:00:21 -04:00
|
|
|
from taggit.forms import TagField
|
2018-05-22 12:22:46 -04:00
|
|
|
from taggit.models import Tag
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2018-06-27 16:02:34 -04:00
|
|
|
from dcim.models import Region
|
2018-07-10 16:16:23 -04:00
|
|
|
from utilities.forms import add_blank_choice, BootstrapMixin, BulkEditForm, LaxURLField, JSONField, SlugField
|
2018-06-20 13:52:54 -04:00
|
|
|
from .constants import (
|
|
|
|
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
|
|
|
|
OBJECTCHANGE_ACTION_CHOICES,
|
|
|
|
)
|
2018-06-27 16:02:34 -04:00
|
|
|
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange
|
2016-08-12 17:20:01 -04:00
|
|
|
|
|
|
|
|
2018-05-22 12:22:46 -04:00
|
|
|
#
|
|
|
|
# Custom fields
|
|
|
|
#
|
|
|
|
|
2016-08-23 16:45:26 -04:00
|
|
|
def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False):
|
2016-08-17 13:40:22 -04:00
|
|
|
"""
|
|
|
|
Retrieve all CustomFields applicable to the given ContentType
|
|
|
|
"""
|
2016-08-23 16:45:26 -04:00
|
|
|
field_dict = OrderedDict()
|
2018-02-21 15:40:11 -05:00
|
|
|
custom_fields = CustomField.objects.filter(obj_type=content_type)
|
2016-08-23 16:45:26 -04:00
|
|
|
if filterable_only:
|
2018-02-21 15:40:11 -05:00
|
|
|
custom_fields = custom_fields.exclude(filter_logic=CF_FILTER_DISABLED)
|
2016-08-16 14:57:04 -04:00
|
|
|
|
|
|
|
for cf in custom_fields:
|
|
|
|
field_name = 'cf_{}'.format(str(cf.name))
|
2018-02-02 21:30:16 -05:00
|
|
|
initial = cf.default if not bulk_edit else None
|
2016-08-16 14:57:04 -04:00
|
|
|
|
|
|
|
# Integer
|
|
|
|
if cf.type == CF_TYPE_INTEGER:
|
2018-02-02 21:30:16 -05:00
|
|
|
field = forms.IntegerField(required=cf.required, initial=initial)
|
2016-08-16 14:57:04 -04:00
|
|
|
|
|
|
|
# Boolean
|
|
|
|
elif cf.type == CF_TYPE_BOOLEAN:
|
|
|
|
choices = (
|
|
|
|
(None, '---------'),
|
2016-09-28 09:58:59 -04:00
|
|
|
(1, 'True'),
|
|
|
|
(0, 'False'),
|
2016-08-16 14:57:04 -04:00
|
|
|
)
|
2018-02-21 15:40:11 -05:00
|
|
|
if initial is not None and initial.lower() in ['true', 'yes', '1']:
|
2017-01-04 09:47:26 -05:00
|
|
|
initial = 1
|
2018-02-21 15:40:11 -05:00
|
|
|
elif initial is not None and initial.lower() in ['false', 'no', '0']:
|
2017-01-04 09:47:26 -05:00
|
|
|
initial = 0
|
2016-08-18 11:44:40 -04:00
|
|
|
else:
|
|
|
|
initial = None
|
2018-02-02 21:30:16 -05:00
|
|
|
field = forms.NullBooleanField(
|
|
|
|
required=cf.required, initial=initial, widget=forms.Select(choices=choices)
|
|
|
|
)
|
2016-08-16 14:57:04 -04:00
|
|
|
|
|
|
|
# Date
|
|
|
|
elif cf.type == CF_TYPE_DATE:
|
2018-02-02 21:30:16 -05:00
|
|
|
field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD")
|
2016-08-16 14:57:04 -04:00
|
|
|
|
|
|
|
# Select
|
|
|
|
elif cf.type == CF_TYPE_SELECT:
|
2016-09-16 13:41:53 -04:00
|
|
|
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
2016-12-07 14:00:52 -05:00
|
|
|
if not cf.required or bulk_edit or filterable_only:
|
2016-08-16 14:57:04 -04:00
|
|
|
choices = [(None, '---------')] + choices
|
2018-06-29 16:01:28 -04:00
|
|
|
# Check for a default choice
|
|
|
|
default_choice = None
|
|
|
|
if initial:
|
|
|
|
try:
|
|
|
|
default_choice = cf.choices.get(value=initial).pk
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
pass
|
|
|
|
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-08-23 16:45:26 -04:00
|
|
|
# URL
|
|
|
|
elif cf.type == CF_TYPE_URL:
|
2018-02-02 21:30:16 -05:00
|
|
|
field = LaxURLField(required=cf.required, initial=initial)
|
2016-08-23 16:45:26 -04:00
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
# Text
|
|
|
|
else:
|
2018-02-02 21:30:16 -05:00
|
|
|
field = forms.CharField(max_length=255, required=cf.required, initial=initial)
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
field.model = cf
|
2016-08-18 14:23:28 -04:00
|
|
|
field.label = cf.label if cf.label else cf.name.replace('_', ' ').capitalize()
|
2016-12-16 10:54:45 -05:00
|
|
|
if cf.description:
|
|
|
|
field.help_text = cf.description
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
field_dict[field_name] = field
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
return field_dict
|
2016-08-12 17:20:01 -04:00
|
|
|
|
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
class CustomFieldForm(forms.ModelForm):
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
def __init__(self, *args, **kwargs):
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-10-05 15:17:17 -04:00
|
|
|
self.custom_fields = []
|
2016-08-16 14:57:04 -04:00
|
|
|
self.obj_type = ContentType.objects.get_for_model(self._meta.model)
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
super(CustomFieldForm, self).__init__(*args, **kwargs)
|
2016-08-12 17:20:01 -04:00
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
# Add all applicable CustomFields to the form
|
2016-08-17 13:40:22 -04:00
|
|
|
custom_fields = []
|
2016-08-16 14:57:04 -04:00
|
|
|
for name, field in get_custom_fields_for_model(self.obj_type).items():
|
|
|
|
self.fields[name] = field
|
2016-08-17 13:40:22 -04:00
|
|
|
custom_fields.append(name)
|
|
|
|
self.custom_fields = custom_fields
|
2016-08-15 15:24:23 -04:00
|
|
|
|
|
|
|
# If editing an existing object, initialize values for all custom fields
|
|
|
|
if self.instance.pk:
|
2016-08-16 14:57:04 -04:00
|
|
|
existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
|
2016-08-15 15:24:23 -04:00
|
|
|
.select_related('field')
|
|
|
|
for cfv in existing_values:
|
2016-09-28 09:58:59 -04:00
|
|
|
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
|
2016-08-15 15:24:23 -04:00
|
|
|
|
|
|
|
def _save_custom_fields(self):
|
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
for field_name in self.custom_fields:
|
2016-08-23 16:45:26 -04:00
|
|
|
try:
|
|
|
|
cfv = CustomFieldValue.objects.select_related('field').get(field=self.fields[field_name].model,
|
|
|
|
obj_type=self.obj_type,
|
|
|
|
obj_id=self.instance.pk)
|
|
|
|
except CustomFieldValue.DoesNotExist:
|
|
|
|
# Skip this field if none exists already and its value is empty
|
2017-05-24 11:33:11 -04:00
|
|
|
if self.cleaned_data[field_name] in [None, '']:
|
2016-08-23 16:45:26 -04:00
|
|
|
continue
|
|
|
|
cfv = CustomFieldValue(
|
|
|
|
field=self.fields[field_name].model,
|
|
|
|
obj_type=self.obj_type,
|
|
|
|
obj_id=self.instance.pk
|
|
|
|
)
|
|
|
|
cfv.value = self.cleaned_data[field_name]
|
|
|
|
cfv.save()
|
2016-08-15 15:24:23 -04:00
|
|
|
|
|
|
|
def save(self, commit=True):
|
|
|
|
obj = super(CustomFieldForm, self).save(commit)
|
|
|
|
|
|
|
|
# Handle custom fields the same way we do M2M fields
|
|
|
|
if commit:
|
|
|
|
self._save_custom_fields()
|
|
|
|
else:
|
|
|
|
self.save_custom_fields = self._save_custom_fields
|
|
|
|
|
|
|
|
return obj
|
2016-08-16 14:57:04 -04:00
|
|
|
|
|
|
|
|
2016-09-30 16:17:41 -04:00
|
|
|
class CustomFieldBulkEditForm(BulkEditForm):
|
2016-08-16 14:57:04 -04:00
|
|
|
|
2016-10-05 15:17:17 -04:00
|
|
|
def __init__(self, *args, **kwargs):
|
2016-08-16 14:57:04 -04:00
|
|
|
super(CustomFieldBulkEditForm, self).__init__(*args, **kwargs)
|
|
|
|
|
2016-10-05 15:17:17 -04:00
|
|
|
self.custom_fields = []
|
|
|
|
self.obj_type = ContentType.objects.get_for_model(self.model)
|
|
|
|
|
2016-08-16 14:57:04 -04:00
|
|
|
# Add all applicable CustomFields to the form
|
2016-10-05 15:17:17 -04:00
|
|
|
custom_fields = get_custom_fields_for_model(self.obj_type, bulk_edit=True).items()
|
|
|
|
for name, field in custom_fields:
|
|
|
|
# Annotate non-required custom fields as nullable
|
|
|
|
if not field.required:
|
|
|
|
self.nullable_fields.append(name)
|
2016-08-16 14:57:04 -04:00
|
|
|
field.required = False
|
|
|
|
self.fields[name] = field
|
2016-10-05 15:17:17 -04:00
|
|
|
# Annotate this as a custom field
|
|
|
|
self.custom_fields.append(name)
|
2016-08-23 12:05:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
class CustomFieldFilterForm(forms.Form):
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
|
|
self.obj_type = ContentType.objects.get_for_model(self.model)
|
|
|
|
|
|
|
|
super(CustomFieldFilterForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
# Add all applicable CustomFields to the form
|
2016-08-23 16:45:26 -04:00
|
|
|
custom_fields = get_custom_fields_for_model(self.obj_type, filterable_only=True).items()
|
2016-08-23 12:05:28 -04:00
|
|
|
for name, field in custom_fields:
|
|
|
|
field.required = False
|
|
|
|
self.fields[name] = field
|
2017-03-30 21:55:57 -04:00
|
|
|
|
|
|
|
|
2018-05-22 12:22:46 -04:00
|
|
|
#
|
|
|
|
# Tags
|
|
|
|
#
|
|
|
|
|
|
|
|
class TagForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
slug = SlugField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Tag
|
|
|
|
fields = ['name', 'slug']
|
|
|
|
|
|
|
|
|
2018-07-10 10:00:21 -04:00
|
|
|
class AddRemoveTagsForm(forms.Form):
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(AddRemoveTagsForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
# Add add/remove tags fields
|
|
|
|
self.fields['add_tags'] = TagField(required=False)
|
|
|
|
self.fields['remove_tags'] = TagField(required=False)
|
|
|
|
|
|
|
|
|
2018-06-27 16:02:34 -04:00
|
|
|
#
|
|
|
|
# Config contexts
|
|
|
|
#
|
|
|
|
|
|
|
|
class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
regions = TreeNodeMultipleChoiceField(
|
|
|
|
queryset=Region.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
2018-07-10 16:16:23 -04:00
|
|
|
data = JSONField()
|
2018-06-27 16:02:34 -04:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ConfigContext
|
2018-06-29 09:44:32 -04:00
|
|
|
fields = [
|
2018-07-27 15:47:29 -04:00
|
|
|
'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'tenant_groups',
|
|
|
|
'tenants', 'data',
|
2018-06-29 09:44:32 -04:00
|
|
|
]
|
2018-06-27 16:02:34 -04:00
|
|
|
|
|
|
|
|
2018-05-22 12:22:46 -04:00
|
|
|
#
|
|
|
|
# Image attachments
|
|
|
|
#
|
|
|
|
|
2017-03-30 21:55:57 -04:00
|
|
|
class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ImageAttachment
|
|
|
|
fields = ['name', 'image']
|
2018-06-20 13:52:54 -04:00
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Change logging
|
|
|
|
#
|
|
|
|
|
|
|
|
class ObjectChangeFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|
|
|
model = ObjectChange
|
|
|
|
q = forms.CharField(
|
|
|
|
required=False,
|
|
|
|
label='Search'
|
|
|
|
)
|
|
|
|
# TODO: Change time_0 and time_1 to time_after and time_before for django-filter==2.0
|
|
|
|
time_0 = forms.DateTimeField(
|
|
|
|
label='After',
|
|
|
|
required=False,
|
|
|
|
widget=forms.TextInput(
|
|
|
|
attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
time_1 = forms.DateTimeField(
|
|
|
|
label='Before',
|
|
|
|
required=False,
|
|
|
|
widget=forms.TextInput(
|
|
|
|
attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
action = forms.ChoiceField(
|
|
|
|
choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
user = forms.ModelChoiceField(
|
|
|
|
queryset=User.objects.order_by('username'),
|
|
|
|
required=False
|
|
|
|
)
|