class FieldSchema(models.Model): _PROHIBITED_NAMES = ('__module__', '_schema', '_declared') _MAX_LENGTH_DATA_TYPES = ('character', ) name = models.CharField(max_length=63) model_schema = models.ForeignKey(ModelSchema, on_delete=models.CASCADE, related_name='fields') data_type = models.CharField(max_length=16, choices=FieldFactory.data_type_choices(), editable=False) null = models.BooleanField(default=False) unique = models.BooleanField(default=False) max_length = models.PositiveIntegerField(null=True) class Meta: unique_together = (('name', 'model_schema'), ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._initial_name = self.name self._initial_null = self.null self._initial_field = self.get_registered_model_field() self._schema_editor = FieldSchemaEditor(self._initial_field) def save(self, **kwargs): self.validate() super().save(**kwargs) self.update_last_modified() model, field = self._get_model_with_field() self._schema_editor.update_column(model, field) def delete(self, **kwargs): model, field = self._get_model_with_field() self._schema_editor.drop_column(model, field) self.update_last_modified() super().delete(**kwargs) def validate(self): if self._initial_null and not self.null: raise NullFieldChangedError( f"Cannot change NULL field '{self.name}' to NOT NULL") if self.name in self.get_prohibited_names(): raise InvalidFieldNameError( f'{self.name} is not a valid field name') def get_registered_model_field(self): latest_model = self.model_schema.get_registered_model() if latest_model and self.name: try: return latest_model._meta.get_field(self.name) except FieldDoesNotExist: pass @classmethod def get_prohibited_names(cls): # TODO: return prohbited names based on backend return cls._PROHIBITED_NAMES @classmethod def get_data_types(cls): return FieldFactory.get_data_types() @property def db_column(self): return slugify(self.name).replace('-', '_') def requires_max_length(self): return self.data_type in self.__class__._MAX_LENGTH_DATA_TYPES def update_last_modified(self): self.model_schema.last_modified = timezone.now() def get_options(self): """ Get a dictionary of kwargs to be passed to the Django field constructor """ options = {'null': self.null, 'unique': self.unique} if self.requires_max_length(): options[ 'max_length'] = self.max_length or config.default_charfield_max_length( ) return options def _get_model_with_field(self): model = self.model_schema.as_model() try: field = model._meta.get_field(self.db_column) except FieldDoesNotExist: field = None return model, field
class FieldSchema(models.Model): _PROHIBITED_NAMES = ("__module__", "_declared") name = models.CharField(max_length=63) model_schema = models.ForeignKey(ModelSchema, on_delete=models.CASCADE, related_name="fields") class_name = models.TextField() kwargs = FieldKwargsJSON(default=dict) class Meta: unique_together = (("name", "model_schema"),) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._initial_name = self.name self._initial_null = self.null self._initial_field = self.get_registered_model_field() self._schema_editor = FieldSchemaEditor(self._initial_field) def save(self, **kwargs): self.validate() super().save(**kwargs) self.update_last_modified() model, field = self._get_model_with_field() self._schema_editor.update_column(model, field) def delete(self, **kwargs): model, field = self._get_model_with_field() self._schema_editor.drop_column(model, field) self.update_last_modified() super().delete(**kwargs) def validate(self): if self._initial_null and not self.null: raise NullFieldChangedError(f"Cannot change NULL field '{self.name}' to NOT NULL") if self.name in self.get_prohibited_names(): raise InvalidFieldNameError(f"{self.name} is not a valid field name") def get_registered_model_field(self): latest_model = self.model_schema.get_registered_model() if latest_model and self.name: try: return latest_model._meta.get_field(self.name) except FieldDoesNotExist: pass @classmethod def get_prohibited_names(cls): # TODO: return prohbited names based on backend return cls._PROHIBITED_NAMES @property def db_column(self): return slugify(self.name).replace("-", "_") @property def null(self): return self.kwargs.get("null", False) @null.setter def null(self, value): self.kwargs['null'] = value def update_last_modified(self): cache.update_last_modified(self.model_schema.initial_model_name) def get_options(self): return self.kwargs.copy() def _get_model_with_field(self): model = self.model_schema.as_model() try: field = model._meta.get_field(self.db_column) except FieldDoesNotExist: field = None return model, field