Django Models (dynamic?)

Viewed 916

I am just starting Django Website;

I have deciced to go with a model (with SQLite DB), which have the following properties:

class Flow(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE,
                              verbose_name="Owner", default="ADMIN")

    source = models.CharField(default='HTTP', choices=SOURCE_CHOICES, editable=True, max_length=12)
    name = models.CharField(max_length=50, default=" ")
    date = models.DateTimeField(verbose_name="Creation date")

I want to add others fields to this model depending on the source field value.

For example if the source field : 'File' is selected. I will create additionnal field (like file name, file directory ...) If 'Http' is selected, I will create a field URL. Thus depending on the source field, I will have differents field and type.

I have read this kind of model is difficult to reprensent in Django; I am open minded to other kind of solution. My idea was to created as many model as the possible source value.

2 Answers

Even if you can create dynamic field in Django, you can't create dynamic column in Sqlite table FLOW.

If you plan to use same types of fields in different cases, you can create field with abstract name, for example path. That can be as URL as local file path.

In common way you need to create all columns for all choices for DB table.

class Flow(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE,
                              verbose_name="Owner", default="ADMIN")

    source = models.CharField(default='HTTP', choices=SOURCE_CHOICES, editable=True, max_length=12)
    name = models.CharField(max_length=50, default=" ")
    date = models.DateTimeField(verbose_name="Creation date")
    local_path = models.ImageField(upload_to='files', null=True, blank=True) # when source is file
    url = models.URLField(null=True, blank=True) # when source is http

So local_path may be empty when you using HTTP source.

Inside view you can dynamically get (or set) value through serializer:

class FlowSerializer(serializers.ModelSerializer):
    path = serializers.SerializerMethodField(method_name='get_path_value')

    class Meta:
        model = Flow
        fields = ('owner', 'source', 'name', 'date', 'path')

    def get_path_value(self, instance):
        if instance.source == 'HTTP':
            return instance.url
        else:
            return instance.local_path

So path will be different for different sources. And maybe you will be need to install django rest framework for this solution.

EDIT1: answering to question

So if I understand well, the best pratices should be to create 'blank' columns

You definitely must to describe all columns in table (unless you using non Sql-like DB, such as MongoDB). So yes, create 'blank' columns, is only one possible way.

But you can override save method in model, for dynamically save fields:

class Flow(models.Model):
  temp_path = None
  path = models...
  url = models...
  choice = models...
  
  def save(self, *args, **kwargs):
    if choice == 'HTTP':
      self.url = temp_path
    else:
      self.path = temp_path
    super().save(*args, **kwargs)

Code above is just a quick idea. Not really working code.

You could implement a custom model field type for that, it helps you save a union object in one column in database. The raw type in database could be a JSON string or other serialized types, for the model field's usage, it's just a python native object!

Here is a piece of sample code: the main jobs are these two methods:

class HandField(models.Field):
    # ...

    def from_db_value(self, value, expression, connection):
        if value is None:
            return value
        return parse_hand(value)

    def to_python(self, value):
        if isinstance(value, Hand):
            return value

        if value is None:
            return value

        return parse_hand(value)

Check the official docs for details.

Related