Django одно поле пользовательской модели для двух столбцов бд

#django #django-models #django-rest-framework

Вопрос:

Я хотел бы создать пользовательское поле, которое получает строку даты и времени и хранит данные в двух столбцах БД. Я читаю документ django, но не понимаю, как это сделать с более чем одним столбцом базы данных.

Это устаревшая БД, поэтому я не могу изменить расположение таблиц БД.

У меня есть эта модель прямо сейчас.

 class MyModel(models.Model):
    mixing_datetime = models.DateTimeField()
    mixing_datetime_utc_offset_seconds = models.IntegerField()
 

Я хотел бы создать поле, которое позволяет интерфейсу (это API с DRF) просто отправлять строку даты и времени (вместо даты и смещения отдельно), и пусть серверная часть выполняет работу за кулисами.
Таким образом, идея была бы:

 class MyModel(models.Model):
    mixing_datetime = models.DateTimeField()
    mixing_datetime_utc_offset_seconds = models.IntegerField()
    datetime = MyCustomDatetimeField() # This also should not be sent to db
 

Итак, чтобы уточнить, идея заключается в том, чтобы позволить интерфейсу выполнить этот запрос:

 frontend_client.post('server_url', payload={'datetime': '2021-07-02T14:34:49 00:00'}
 

А затем де бэкэнд произведет некоторые вычисления и установит как mixing_datetime, так и mixing_datetime_utc_offset_seconds

Как я могу этого достичь?

Комментарии:

1. Что именно делает бэкэнд за кулисами? Вы хотите обработать эту строку даты и времени с переднего конца и сохранить ее в mixing_datetime и mixing_datetime_utc_offset_seconds ?

2. @bdbd Я добавляю больше контекста. Передний конец просто отправляет строку. Обработка будет выполняться в бэкэнде

3. Хорошо, теперь я понимаю вашу ситуацию. Я удалил свой ответ, так как он не отвечает на ваш вопрос. Но да, поля пользовательской модели django предназначены для пользовательских полей, которые в конечном итоге будут отражены в базе данных, поэтому, если вы хотите сделать это таким образом, вам, вероятно, придется выполнить много переопределений.

4. Вероятно, вы можете просто написать метод (в классе модели или в виде миксина), который принимает строку datetime и обрабатывает ее для этих двух полей модели. Это должно быть довольно просто обрабатывать в сериализаторах/представлении

Ответ №1:

Поэтому, наконец, я пришел с этим решением. Это не то, о чем я просил (пользовательское поле моделей django), но поскольку я занимаюсь API, я решил создать пользовательское поле сериализатора DRF. Если вы не работаете с DRF, возможно, вы можете перезаписать метод сохранения модели.

Это пользовательское поле сериализатора DRF.

 class CustomDateTimeField(serializers.Field):
    """Custom field used to convert a string in isoformat into other two fields.
    So the caller instead of sending datetime and offset, will only send the string, and internally we 
    are going to split it in the two respective fields.
    Same when caller requests data, it will receive only one datetime field. (output pf to_representation).

    In a model we are going to have two fields that will follow this naming pattern:
        >> datetime = models.DateTimeField()
        >> offset_seconds = models.IntegerField()
    """

    def __init__(self, **kwargs):
        self.prefix_field_name = kwargs.pop("prefix_field_name")
        self.datetime_field = 'datetime'
        self.offset_seconds_field = 'offset_seconds'

        super().__init__(**kwargs)
        # Set this, so we receive all model fields in to_representation.
        self.source = '*'

    def to_representation(self, instance):
        """This function is called when an object instance is serialized. For example an instance retrieved from db.
        For example this serialization will lead into this function:
            >> instance = MyModel.objects.last()
            >> MyModelSerializer(instance).data
        """
        return get_isoformat_string_from_datetime(
            getattr(instance, self.datetime_field), getattr(instance, self.offset_seconds_field)
        )

    def to_internal_value(self, data):
        """This functions is called when a json object is serialized. For example on an Api call.
        The data returned from this function will be used to update the 'validated_data' dictionary.
        For example:
            >> data_to_be_serialized = {'datetime': '2021-04-10T07:51:15.958266-01:00', 'other_field': 10}
            >> MyModelSerializer(data=data_to_be_serialized)
        """
        if not isinstance(data, str):
            raise serializers.ValidationError("Incorrect type. Expected a string")
        if not is_in_isoformat(data):
            raise serializers.ValidationError("Data should be in isoformat")
        datetime = get_datetime_from_string(data)
        # This fields are going to be used to update validated_data dict.
        ret = {
            self.datetime_field: datetime,
            self.offset_seconds_field: get_utc_offset_seconds(datetime),
        }
        return ret
 

Использование:

 class TestModel(models.Model):
    datetime = models.DateTimeField()
    offset_seconds = models.IntegerField()

class MyModelSerializer(serializers.ModelSerializer):
    datetime = CustomDateTimeField()

    class Meta:
        model = TestModel
        fields = ('datetime', )