def customize(self, customizer): # customization is done in the form and in the template value_field_name = self.get_value_field_name() self.fields[value_field_name].help_text = customizer.documentation # you can customize the category of scientific properties, # so set it here... category_customizer = customizer.category self.initial["category_key"] = category_customizer.get_key() if customizer.field_type != "ENUMERATION": atomic_type = customizer.atomic_type if atomic_type: if atomic_type != MetadataAtomicFieldTypes.DEFAULT: custom_widget_class = METADATA_ATOMICFIELD_MAP[atomic_type][0] custom_widget_args = METADATA_ATOMICFIELD_MAP[atomic_type][1] self.fields["atomic_value"].widget = custom_widget_class(**custom_widget_args) # if I changed the widget, then I have to re-add the attributes that were updated in __init__ above # b/c they will have been lost (I only have to do this for atomic fields b/c the widget for enumerations cannot change) update_field_widget_attributes(self.fields["atomic_value"], {"onchange": "copy_value(this,'%s-scientific_property_value');" % self.prefix}) update_field_widget_attributes(self.fields["atomic_value"], {"class": "atomic_value changer"}) update_field_widget_attributes(self.fields["atomic_value"], {"class": atomic_type.lower()}) else: widget_attributes = {"class": "multiselect", } all_enumeration_choices = [(choice, choice) for choice in customizer.enumeration_choices] if customizer.enumeration_nullable: all_enumeration_choices += NULL_CHOICE widget_attributes["class"] += " nullable" if customizer.enumeration_open: all_enumeration_choices += OTHER_CHOICE widget_attributes["class"] += " open" if customizer.enumeration_multi: widget_attributes["class"] += " multiple" self.fields["enumeration_value"].set_choices(all_enumeration_choices, multi=True) else: widget_attributes["class"] += " single" # all_enumeration_choices = EMPTY_CHOICE + all_enumeration_choices self.fields["enumeration_value"].set_choices(all_enumeration_choices, multi=False) update_field_widget_attributes(self.fields["enumeration_value"], widget_attributes) update_field_widget_attributes(self.fields["enumeration_other_value"], {"class": "other"}) # extra_attributes... if not customizer.edit_extra_standard_name: update_field_widget_attributes(self.fields["extra_standard_name"], {"class": "readonly", "readonly": "readonly"}) if not customizer.edit_extra_description: update_field_widget_attributes(self.fields["extra_description"], {"class": "readonly", "readonly": "readonly"}) if not customizer.edit_extra_units: update_field_widget_attributes(self.fields["extra_units"], {"class": "readonly", "readonly": "readonly"}) set_field_widget_attributes(self.fields["extra_description"], {"cols": "60", "rows": "4"}) self.customizer = customizer
def __init__(self, *args, **kwargs): super(QPropertyCustomizationForm, self).__init__(*args, **kwargs) set_field_widget_attributes(self.fields["property_description"], {"rows": 2}) set_field_widget_attributes(self.fields["cardinality"], {"readonly": True, "ng-disabled": "true"}) update_field_widget_attributes(self.fields["cardinality"], {"class": "form-control-auto"}) self.unbootstrap_fields(["inline_help", "is_required", "is_hidden", "is_editable", "is_nillable"]) field_type = self.get_current_field_value("field_type") if field_type == QPropertyTypes.ATOMIC: update_field_widget_attributes(self.fields["atomic_type"], {"class": "select single show-tick"}) set_field_widget_attributes(self.fields["atomic_suggestions"], {"rows": 2}) set_field_widget_attributes(self.fields["default_values"], {"rows": 2}) elif field_type == QPropertyTypes.ENUMERATION: set_field_widget_attributes(self.fields["default_values"], {"rows": 2}) self.unbootstrap_fields(["enumeration_is_open"]) elif field_type == QPropertyTypes.RELATIONSHIP: update_field_widget_attributes(self.fields["relationship_show_subforms"], {"readonly": True, "ng-disabled": "true"}) update_field_widget_attributes(self.fields["relationship_is_hierarchical"], {"readonly": True, "ng-disabled": "true"}) self.unbootstrap_fields(["relationship_show_subforms", "relationship_is_hierarchical"]) else: msg = "Unknown field type: {0}".format(field_type) raise QError(msg) if self.instance.proxy.is_required: update_field_widget_attributes(self.fields["is_required"], {"readonly": True, "ng-disabled": "true"}) if self.instance.has_specialized_values: update_field_widget_attributes(self.fields["default_values"], {"readonly": True, "ng-disabled": "true"}) update_field_widget_attributes(self.fields["is_editable"], {"readonly": True, "ng-disabled": "true"})
def __init__(self, *args, **kwargs): super(QCategoryCustomizationForm, self).__init__(*args, **kwargs) self.unbootstrap_field("is_hidden") update_field_widget_attributes(self.fields["category_title"], {"class": "form-control form-control-small"}) update_field_widget_attributes(self.fields["category_description"], {"class": "form-control form-control-small"}) set_field_widget_attributes(self.fields["category_description"], {"rows": 2}) update_field_widget_attributes(self.fields["order"], {"ng-disabled": "true", "disabled": "disabled"})
def bootstrap_field(field): bootstrap_classes = { "class": "form-control", } update_field_widget_attributes(field, bootstrap_classes) if not isinstance(field, BooleanField): set_field_widget_attributes(field, { "placeholder": field.label, })
def __init__(self, *args, **kwargs): form_name = kwargs.get("form_name", None) assert form_name, "QForm must have a unique name." super(QForm, self).__init__(*args, **kwargs) # add the possibility of rendering a server error on _all_ fields for field_name, field in self.fields.items(): set_field_widget_attributes(field, { "servererror": "true", })
def __init__(self, *args, **kwargs): super(QCategoryCustomizationForm, self).__init__(*args, **kwargs) if not self.instance.pk: # I don't need to reset the key b/c "get_new_customizations" passes it in via initial pass else: # TODO: DOUBLE-CHECK THAT I SHOULD SET ".initial[key]" and not ".fields[key].initial" # self.fields["key"].initial = self.instance.get_key() self.initial["key"] = self.instance.get_key() set_field_widget_attributes(self.fields["documentation"], {"rows": 2}) update_field_widget_attributes(self.fields["order"], {"ng-disabled": "true"})
def __init__(self, *args, **kwargs): super(QUserProfileForm, self).__init__(*args, **kwargs) profile = self.instance user = profile.user self.fields["first_name"].initial = user.first_name self.fields["last_name"].initial = user.last_name self.fields["email"].initial = user.email update_field_widget_attributes(self.fields["email"], {"readonly": True}) set_field_widget_attributes(self.fields["description"], {"rows": 2}) update_field_widget_attributes(self.fields["institute"], {"class": "select single show-tick"})
def add_custom_potential_errors_to_field(self, field_name): form_field = self.fields[field_name] model_field = self.instance.get_field(field_name) # only check fields that have a correspondence to the model, # and that can be edited by the user... if model_field is not None and model_field.editable: custom_validators = [v for v in model_field.validators if isinstance(v, QValidator)] for validator in custom_validators: # this attribute is required for Angular to know about the custom validator set_field_widget_attributes(form_field, {validator.name: "true"}) # and I store the validators for later use in get_field_errors() above setattr(form_field, "custom_potential_errors", custom_validators)
def __init__(self, *args, **kwargs): super(QPropertyRealizationForm, self).__init__(*args, **kwargs) field_type = self.get_current_field_value("field_type") proxy = self.instance.proxy is_nil_field = self.fields["is_nil"] self.unbootstrap_field("is_nil") is_nil_field.help_text = mark_safe( _( "<p>Some properties can be intentionally left blank, provided there is a valid reason for doing so.</p>" "<p>Checking this box will reveal a drop-down menu allowing a reason to be specified.</p>" "<p>If a reason is specified, then any value on the left will be ignored during publication.</p>" ) ) if field_type == QPropertyTypes.ATOMIC: atomic_value_field = self.fields["atomic_value"] set_field_widget_attributes(atomic_value_field, { "ng-disabled": "current_model.is_nil" }) elif field_type == QPropertyTypes.ENUMERATION: enumeration_value_field = self.fields["enumeration_value"] enumeration_other_field = self.fields["enumeration_other_value"] # TODO: "complete_choices", "is_multiple", etc. OUGHT TO HAVE BEEN SETUP IN "QPropertyRealization.__init__" # enumeration_value_field._complete_choices = proxy.enumeration_choices enumeration_value_field._is_multiple = proxy.is_multiple # just a one-off here: "enumeration_other_field" is not required on the model # but it is required if displayed in the form... custom_not_blank_validator = ValidateNotBlank() self.add_custom_form_validator_to_field(enumeration_other_field, custom_not_blank_validator) set_field_widget_attributes(enumeration_other_field, { "placeholder": ENUMERATION_OTHER_PLACEHOLDER, "ng-show": "value_in_array('{0}', current_model.enumeration_value)".format(ENUMERATION_OTHER_CHOICE), # angular validation wasn't running on blank fields # this prevented my "valildate_not_blank" fn from being called # turning off whitespace trimming solves this problem "ng-trim": "false" }) set_field_widget_attributes(enumeration_value_field, { "ng-disabled": "current_model.is_nil" }) set_field_widget_attributes(enumeration_other_field, { "ng-disabled": "current_model.is_nil" }) else: # field_type == QPropertyTypes.RELATIONSHIP: pass self.is_multiple = proxy.is_multiple # TODO: SHOULD THIS BE DEFERRED FROM DJANGO TO NG? IT IS GUI LOGIC, BUT STILL THIS SEEMS KIND OF MESSY # TODO: (ONE REASON NOT TO MOVE IT TO NG IS THAT I WANT TO CHECK IF I SHOULD RENDER A CATEGORY/PROPERTY _BEFORE_ I DEFINE THE CORRESPONDING NG CONTROLLER) self.category_key = self.instance.category_key self.property_key = self.instance.key
def __init__(self, *args, **kwargs): super(QScientificPropertyCustomizationForm, self).__init__(*args, **kwargs) is_new_property = not self.instance.pk proxy = self.get_proxy() field_type = self.get_field_type() if field_type == QPropertyTypes.ATOMIC: # things to do for ATOMIC fields... # TODO: THIS IS NO LONGER TRUE; "atomic_type" IS NOW A REQUIRED FIELD W/ A DEFAULT VALUE # # since atomic_fields only shows up if this is an ATOMIC field, it cannot be required # # but when I do display it, I can force users to make a choice # atomic_type_field = self.fields["atomic_type"] # atomic_type_field.required = True # atomic_type_field.empty_label = None # atomic_type_field.choices.remove(EMPTY_CHOICE[0]) set_field_widget_attributes(self.fields["atomic_suggestions"], {"rows": 2}) update_field_widget_attributes(self.fields["atomic_type"], {"class": "select single show-tick"}) elif field_type == QPropertyTypes.ENUMERATION: # things to do for ENUMERATION fields... self.unbootstrap_fields(["enumeration_open", "enumeration_multi", "enumeration_nullable", ]) choices = proxy.enumeration_choices.split('|') enumeration_choices_field = self.fields["enumeration_choices"] enumeration_default_field = self.fields["enumeration_default"] enumeration_choices_field.set_choices(choices) enumeration_default_field.set_choices(choices) update_field_widget_attributes(enumeration_choices_field, {"class": "select multiple show-tick"}) update_field_widget_attributes(enumeration_default_field, {"class": "select multiple show-tick"}) if is_new_property: self.initial["enumeration_choices"] = choices else: # field_type == QPropertyTypes.RELATIONSHIP msg = "ScientificProperties cannot be RELATIONSHIPS" raise QError(msg) # things to do for ALL fields... # TODO: DOUBLE-CHECK THAT I SHOULD SET ".initial[key]" and not ".fields[key].initial" self.initial["key"] = self.instance.get_key() self.initial["category_key"] = self.instance.category.get_key() self.unbootstrap_fields(self._extra_fields + ["displayed", "required", "editable", "unique", "inline_help", ]) set_field_widget_attributes(self.fields["documentation"], {"rows": 2})
def __init__(self, *args, **kwargs): super(QModelCustomizationForm, self).__init__(*args, **kwargs) # deal w/ customization_fields... self.unbootstrap_field("is_default") set_field_widget_attributes(self.fields["description"], {"rows": 2}) # deal w/ document_fields... self.unbootstrap_field("model_show_all_categories") set_field_widget_attributes(self.fields["model_description"], {"rows": 2}) # TODO: MAKE SURE THIS LINE IS NOT REPLICATED IN SUBFORMS update_field_widget_attributes(self.fields["name"], { # notice I'm using ng-blur instead of ng-change # ...this is much more efficient "ng-blur": "update_names({field_scope})".format( field_scope=self.get_qualified_model_field_name("name"), ), })
def __init__(self, *args, **kwargs): super(QModelCustomizationForm, self).__init__(*args, **kwargs) # deal w/ customization_fields... self.unbootstrap_field("is_default") self.add_server_errors_to_field("name") self.add_server_errors_to_field("is_default") set_field_widget_attributes(self.fields["documentation"], {"rows": 2}) # deal w/ document_fields... self.unbootstrap_field("model_show_empty_categories") set_field_widget_attributes(self.fields["model_description"], {"rows": 2}) update_field_widget_attributes(self.fields["name"], { # notice I'm using ng-blur instead of ng-change # ...this is much more efficient "ng-blur": "update_names({field_scope})".format( field_scope=self.get_qualified_model_field_name("name"), ), })
def add_custom_form_validator_to_field(self, form_field, custom_validator): """ this is really similar to QForm.add_custom_potential_errors_to_field however, it doesn't assume that the validators come from the underlying model field instead it is used to explicitly customize which validators are fun on the form only (this is useful, for example, when customizing how to render ATOMIC fields) :param form_field: :param custom_validator: :return: """ assert isinstance(custom_validator, QValidator), "{0} is an invalid validator".format(custom_validator) # add this validator to the set of existing errors (if it doesn't already exist)... custom_errors = getattr(form_field, "custom_potential_errors") if custom_validator.name in [e.name for e in custom_errors]: q_logger.error("{0} has already been added as a validator".format(custom_validator)) else: set_field_widget_attributes(form_field, { custom_validator.name: "true" }) custom_errors.append(custom_validator) setattr(form_field, "custom_potential_errors", custom_errors)
def customize(self, customization): field_type = self.get_current_field_value("field_type") proxy = self.instance.proxy assert customization.proxy == proxy, "in QPropertyRealizationForm, customization.proxy doesn't equal instance.proxy" # customize form... self.inline_help = customization.inline_help self.is_nillable = customization.is_nillable self.is_required = customization.is_required self.is_hidden = customization.is_hidden self.is_editable = customization.is_editable self.is_hierarchical = customization.relationship_is_hierarchical self.render = not (self.is_hidden or self.is_hierarchical) # tells the template whether or not I plan on rendering the form # customize fields... self.value_field.help_text = customization.property_description self.value_field.label = customization.property_title self.value_field.required = customization.is_required self.value_field.editable = customization.is_editable if not customization.is_editable: set_field_widget_attributes(self.value_field, { "ng-disabled": "true", "readonly": "readonly", }) set_field_widget_attributes(self.fields["is_nil"], { "ng-disabled": "true", "readonly": "readonly", }) set_field_widget_attributes(self.fields["nil_reason"], { "ng-disabled": "true", "readonly": "readonly", }) # more in-depth customization... if field_type == QPropertyTypes.ATOMIC: atomic_value_field = self.fields["atomic_value"] existing_widget_attrs = atomic_value_field.widget.attrs custom_widget_class, custom_widget_args = ATOMIC_PROPERTY_MAP[customization.atomic_type] atomic_value_field.widget = custom_widget_class(custom_widget_args) update_field_widget_attributes(atomic_value_field, existing_widget_attrs) if self.instance.is_new: default_values = customization.default_values if default_values: # TODO: WILL NEED TO REWRITE THIS TO COPE W/ "cardinality_max" > 1 FOR ATOMIC FIELDS # TODO: (IN THAT CASE, I SHOULD PROBABLY MAKE "atomic_value" A QJSONField assert len(default_values) == 1, "need to rewrite this to cope w/ 'cardinality_max' > 1 for atomic fields" self.set_default_field_value("atomic_value", default_values[0]) self.set_default_field_value("is_complete", True) if customization.is_required: update_field_widget_attributes(atomic_value_field, { "ng-blur": "update_property_completion()", }) else: self.set_default_field_value("is_complete", True) if customization.atomic_suggestions: atomic_suggestions_list = customization.atomic_suggestions.split('|') set_field_widget_attributes(atomic_value_field, { "uib-typeahead": "option for option in [{0}] | filter:$viewValue | limitTo:{1}".format( ",".join(["'{0}'".format(mark_safe(suggestion)) for suggestion in atomic_suggestions_list]), TYPEAHEAD_LIMIT ) }) elif field_type == QPropertyTypes.ENUMERATION: enumeration_value_field = self.fields["enumeration_value"] enumeration_other_value_field = self.fields["enumeration_other_value"] enumeration_choices = copy.copy(proxy.enumeration_choices) # (make a copy of the value so that possibly updating it below doesn't modify the original if customization.enumeration_is_open: enumeration_choices.append({ "value": ENUMERATION_OTHER_CHOICE, "documentation": ENUMERATION_OTHER_DOCUMENTATION, "order": len(enumeration_choices) + 1, }) enumeration_value_field._complete_choices = enumeration_choices if customization.is_required: update_field_widget_attributes(enumeration_value_field, { "ng-blur": "update_property_completion()", }) update_field_widget_attributes(enumeration_other_value_field, { "ng-blur": "update_property_completion()", }) else: self.set_default_field_value("is_complete", True) update_field_widget_attributes(enumeration_other_value_field, { "ng-blur": "update_property_completion()", }) else: # field_type == QPropertyTypes.RELATIONSHIP if not customization.relationship_is_hierarchical: # only have to render non-hierarchical relationships in a property form... self.use_subforms = customization.use_subforms self.use_references = customization.use_references self.cardinality_min = customization.cardinality_min self.cardinality_max = customization.cardinality_max if not customization.is_required: self.set_default_field_value("is_complete", True) self.customization = customization
def __init__(self, *args, **kwargs): super(MetadataModelForm, self).__init__(*args, **kwargs) set_field_widget_attributes(self.fields["title"], {"size": 64})
def __init__(self, *args, **kwargs): super(QProjectForm, self).__init__(*args, **kwargs) for field in self.fields.itervalues(): update_field_widget_attributes(field, {"class": "form-control form-control-md"}) set_field_widget_attributes(self.fields["name"], {"readonly": True, "ng-disabled": "true"}) set_field_widget_attributes(self.fields["description"], {"rows": 2})
def __init__(self, *args, **kwargs): super(QPropertyCustomizationForm, self).__init__(*args, **kwargs) proxy = self.get_proxy() field_type = self.get_field_type() if field_type == QPropertyTypes.ATOMIC: # things to do for ATOMIC fields... # TODO: THIS IS NO LONGER TRUE; "atomic_type" IS NOW A REQUIRED FIELD W/ A DEFAULT VALUE # # since atomic_fields only shows up if this is an ATOMIC field, it cannot be required # # but when I do display it, I can force users to make a choice # atomic_type_field = self.fields["atomic_type"] # atomic_type_field.required = True # atomic_type_field.empty_label = None # atomic_type_field.choices.remove(EMPTY_CHOICE[0]) set_field_widget_attributes(self.fields["atomic_suggestions"], {"rows": 2}) update_field_widget_attributes(self.fields["atomic_type"], {"class": "select single show-tick"}) elif field_type == QPropertyTypes.ENUMERATION: # things to do for ENUMERATION fields... self.unbootstrap_fields(["enumeration_open", ]) # choices = proxy.enumeration_choices.split('|') # enumeration_choices_field = self.fields["enumeration_choices"] # enumeration_default_field = self.fields["enumeration_default"] # enumeration_choices_field.set_choices(choices) # enumeration_default_field.set_choices(choices) # update_field_widget_attributes(enumeration_choices_field, {"class": "select multiple show-tick"}) # update_field_widget_attributes(enumeration_default_field, {"class": "select multiple show-tick"}) # if is_new_property: # self.initial["enumeration_choices"] = choices else: # field_type == QPropertyTypes.RELATIONSHIP # things to do for RELATIONSHIP fields... self.unbootstrap_fields(["relationship_show_subform"]) update_field_widget_attributes(self.fields["relationship_show_subform"], { "ng-disabled": "true", "readonly": "readonly", }) self.fields["subform_targets"].choices = [ (relationship_subform_customization.get_key(), str(relationship_subform_customization.proxy)) for relationship_subform_customization in self.instance.relationship_target_model_customizations(manager="allow_unsaved_relationship_target_model_customizations_manager").all() ] update_field_widget_attributes(self.fields["subform_targets"], {"class": "select single show-tick"}) # things to do for ALL fields... # TODO: DOUBLE-CHECK THAT I SHOULD SET ".initial[key]" and not ".fields[key].initial" self.initial["key"] = self.instance.get_key() self.initial["category_key"] = self.instance.category.get_key() if proxy.is_required(): # if a property is required by the CIM # (then property.reset() will have set the "required" field to True) # then don't allow users to change the "is_required" or "is_hidden" fields update_field_widget_attributes(self.fields["is_required"], { "ng-disabled": "true", "readonly": "readonly", }) update_field_widget_attributes(self.fields["is_hidden"], { "ng-disabled": "true", "readonly": "readonly", }) self.unbootstrap_fields(["is_hidden", "is_required", "is_editable", "is_nillable", "inline_help", ]) set_field_widget_attributes(self.fields["documentation"], {"rows": 2})
def __init__(self, *args, **kwargs): super(QStandardPropertyCustomizationForm, self).__init__(*args, **kwargs) is_new_property = not self.instance.pk proxy = self.get_proxy() field_type = self.get_field_type() if field_type == QPropertyTypes.ATOMIC: # things to do for ATOMIC fields... # TODO: THIS IS NO LONGER TRUE; "atomic_type" IS NOW A REQUIRED FIELD W/ A DEFAULT VALUE # # since atomic_fields only shows up if this is an ATOMIC field, it cannot be required # # but when I do display it, I can force users to make a choice # atomic_type_field = self.fields["atomic_type"] # atomic_type_field.required = True # atomic_type_field.empty_label = None # atomic_type_field.choices.remove(EMPTY_CHOICE[0]) set_field_widget_attributes(self.fields["atomic_suggestions"], {"rows": 2}) update_field_widget_attributes(self.fields["atomic_type"], {"class": "select single show-tick"}) elif field_type == QPropertyTypes.ENUMERATION: # things to do for ENUMERATION fields... self.unbootstrap_fields(["enumeration_open", "enumeration_multi", "enumeration_nullable", ]) choices = proxy.enumeration_choices.split('|') enumeration_choices_field = self.fields["enumeration_choices"] enumeration_default_field = self.fields["enumeration_default"] enumeration_choices_field.set_choices(choices) enumeration_default_field.set_choices(choices) update_field_widget_attributes(enumeration_choices_field, {"class": "select multiple show-tick"}) update_field_widget_attributes(enumeration_default_field, {"class": "select multiple show-tick"}) if is_new_property: self.initial["enumeration_choices"] = choices else: # field_type == QPropertyTypes.RELATIONSHIP # things to do for RELATIONSHIP fields... self.unbootstrap_fields(["relationship_show_subform"]) update_field_widget_attributes(self.fields["relationship_show_subform"], { "ng-disabled": "true", "readonly": "readonly", }) update_field_widget_attributes(self.fields["cardinality"], { "class": "cardinality", }) # things to do for ALL fields... # TODO: DOUBLE-CHECK THAT I SHOULD SET ".initial[key]" and not ".fields[key].initial" self.initial["key"] = self.instance.get_key() try: self.initial["category_key"] = self.instance.category.get_key() except AttributeError: # sometimes properties in subforms don't have categories # ...that's okay pass if proxy.is_required(): # if a property is required by the CIM # (then property.reset() will have set the "required" field to True) # then don't allow users to change the "required" or "displayed" fields update_field_widget_attributes(self.fields["required"], { "ng-disabled": "true", "readonly": "readonly", }) update_field_widget_attributes(self.fields["displayed"], { "ng-disabled": "true", "readonly": "readonly", }) self.unbootstrap_fields(["displayed", "required", "editable", "unique", "inline_help", "inherited", ]) set_field_widget_attributes(self.fields["documentation"], {"rows": 2})
def add_server_errors_to_field(self, field_name): # adds the possibility of rendering a server error on a given field field = self.fields[field_name] set_field_widget_attributes(field, { "servererror": "true", })