'Django: Create a Model from parsing a different model's field

I have a model A with several fields. One of the fields ("results") is a dict-like string in my database (which is hardly readable for a human being). Can I create a separate Model that would parse that "results" field into its fields to have a different table with fields corresponding to the keys and values from my "results" field from model A? The final goal is to make a Results table that shows all the information pretty and easy-to-read manner.

class ModelA(models.Model):
    language = models.CharField(max_length=30)
    date = models.DateTimeField()
    results = models.CharField(max_length=255)

This is how "results" field looks in my database (I cannot change this format):

OrderedDict([('Name', 'Bob'), ('Phone', '1234567890'), ('born', '01.01.1990')])

I want to create something like this:

class Results(models.Model):
    name = model.Charfield(max_length=100)
    phone= model.IntegerField()
    born = model.DateTimeField()

What is the best way to do this? How do I take the info from the "results" field from ModelA and "put" it into the Results model?



Solution 1:[1]

You can make a function that transforms it back to a dictionary with:

from ast import literal_eval
from datetime import datetime

def results_process(text):
    if not (text.startswith('OrderedDict(') and text.endswith(')')):
        raise ValueError('Invalid format')
    data = dict(literal_eval(text[12:-1]))
    return Results.objects.create(
        name=data['Name'],
        phone=data['Phone'],
        born=datetime.strptime(data['born'], '%d.%m.%Y')
    )

for a given string contained in results of a ModelA object, it will thus construct a Results model object stored in the database.

Solution 2:[2]

I would say to link the two models with a foreignKey so accessing them as a dictionary in your view. link so:

class ModelA(models.Model):
    language = models.CharField(max_length=30)
    date = models.DateTimeField()
    results = models.CharField(max_length=255)

class Results(models.Model):
    # Here we are creating a relationship between the two model
    # so that we can get their data by related names.
    model_a = models.ForeignKey(MadelA,on_delete=models.CASCADE)
    name = model.Charfield(max_length=100)
    phone= model.IntegerField()
    born = model.DateTimeField()

In our view we can get data to both models by using its related name, as django would provide a default related name if one is not provided as in my solution, so the related name for the two models would be results_set which would be use to retrieved our data for both model in our view, whether in dictionary,list or tuple when we query the data from the backend.


getting both data by related name

ModelA.objects.prefetch_related('results_set').dict()



Solution 3:[3]

You can create properties on the model, which will make the key:value pairs in that dict appear as if they are ordinary model fields for a lot of coding purposes (but not querysets or ModelForms)

In passing I'd really really want to change results to a JSONfield, but I'll assume you aren't using PostgreSQL ...

class ModelA(models.Model):
    results = models.CharField(max_length=255)

    @property
    def  name(self):
        if not hasattr(self, '_rdict'):
            self._parse_results()
        return self._rdict['name']
    @name.setter
    def name( self, value):
        if not hasattr(self, '_rdict'):
            self._parse_results()
        self._rdict['name'] = value

    # similarly for the other keys in results
    # if thre are lots of keys, to be DRY investigate the property builtin
    # field = property( getter, setter)

    def _parse_results(self):
        d = OrderedDict() 
        # use self.results to turn results back into an OrderedDict
        # code (use python re module? ) tbs
        # set d['name'] = parsed_name  etc in the right order!

        self._rdict = d

    def save( self, *args, **kwargs):
        # rewrite results to reflect _rdict contents
        # this is a hack implementation 
        # (but I suspect it's how results is generated)

        self.results = str( self._rdict)
        super().save(*args, **kwargs)

If you just want to be able to read these values as if they were fields, but not update them back to the database, you can dispense with a lot of this:

class ModelA(models.Model):
    results = models.CharField(max_length=255)

    @property
    def  name(self):
        if not hasattr(self, '_rdict'):
            self._parse_results()
        return self._rdict['name']

    def _parse_results(self):
       # as above

    # def save( ...) is not needed if the properties are read-only

You might save all those hasattr tests if you instead decoded results in the model's __init__ after super().__init__ called. I don't know why, but the Django doc warns against this (or used to). I have done it with one of my models, and so far no ill consequences I am aware of. Interfering before the superclass does its magic may be what it was warning against. I can't find the warning now.

If you wanted to use (say) modelforms based on the "fields" in results. there's another way that might help: multi-table inheritance. I don't know enough to recommend this. It creates an automagical OneToOne relation between the current DB table and your new model. You'd then have to subclass the __init__ method of your model to decode the results. Good luck if you attempt this (and if you do get it working, post what you did back here as an answer to your own question, because I'd like to know! )

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Willem Van Onsem
Solution 2 Godda
Solution 3