Question

I have a model that looks like this and stores data as key-value pairs.

class Setting(models.Model):
    company = models.ForeignKey(
        Company
    )
    name = models.CharField(
        null=False, max_length=255
    )
    value= models.CharField(
        null=False, max_length=255
    )

I have a custom Manager on this Model which overrides the get method. When the queries my Model like Settings.objects.get(company=1), I use my over-ridden get method to execute a self.objects.filter(company=1) which returns a list of objects. Can I generate one single custom QuerySet which has all the key-value pairs as fields.

Example:

If the data in my Model was like this:

company  name    value
-------  ----    -----
1        theme   custom
1        mode    fast
1        color   green

I'd like to return a query set that would be pivoted like so when someone executed Settings.objects.get(company=1):

company  theme    mode     color
------   -----    ----     -----
1        custom   fast     green

I've tried to be verbose but do let me know if I should explain better. I'm not sure if the Django Models allow this scenario.

Thank you everyone.


Edit: Using Proxy models

Is this something I could accomplish using Proxy Models i.e. having a base model to store the key value fields and custom proxy model with normal get and save method?

Was it helpful?

Solution

Here's how I did it.

I needed to do this because I had a Model that stored information as key value pairs and I needed to build a ModelForm on that Model but the ModelForm should display the key-value pairs as fields i.e. pivot the rows to columns. By default, the get() method of the Model always returns a Model instance of itself and I needed to use a custom Model. Here's what my key-value pair model looked like:

class Setting(models.Model):
    domain = models.ForeignKey(Domain)
    name = models.CharField(null=False, max_length=255)
    value = models.CharField(null=False, max_length=255)

    objects = SettingManager()

I built a custom manager on this to override the get() method:

class SettingManager(models.Manager):

    def get(self, *args, **kwargs):
        from modules.customer.proxies import *
        from modules.customer.models import *

        object = type('DomainSettings', (SettingProxy,), {'__module__' : 'modules.customer'})()
        for pair in self.filter(*args, **kwargs): setattr(object, pair.name, pair.value)

        setattr(object, 'domain', Domain.objects.get(id=int(kwargs['domain__exact'])))
        return object

This Manager would instantiate an instance of this abstract model. (Abstract models don't have tables so Django doesn't throw up errors)

class SettingProxy(models.Model):

    domain = models.ForeignKey(Domain, null=False, verbose_name="Domain")
    theme = models.CharField(null=False, default='mytheme', max_length=16)
    message = models.CharField(null=False, default='Waddup', max_length=64)

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        super(SettingProxy, self).__init__(*args, **kwargs)
        for field in self._meta.fields:
            if isinstance(field, models.AutoField):
                del field

    def save(self, *args, **kwargs):
        with transaction.commit_on_success():
            Setting.objects.filter(domain=self.domain).delete()

            for field in self._meta.fields:
                if isinstance(field, models.ForeignKey) or isinstance(field, models.AutoField):
                    continue
                else:
                    print field.name + ': ' + field.value_to_string(self)
                    Setting.objects.create(domain=self.domain,
                        name=field.name, value=field.value_to_string(self)
                    )

This proxy has all the fields that I'd like display in my ModelFom and store as key-value pairs in my model. Now if I ever needed to add more fields, I could simply modify this abstract model and not have to edit the actual model itself. Now that I have a model, I can simply build a ModelForm on it like so:

class SettingsForm(forms.ModelForm):

    class Meta:
        model = SettingProxy
        exclude = ('domain',)

    def save(self, domain, *args, **kwargs):
        print self.cleaned_data
        commit = kwargs.get('commit', True)
        kwargs['commit'] = False
        setting = super(SettingsForm, self).save(*args, **kwargs)
        setting.domain = domain
        if commit:
            setting.save()
        return setting

I hope this helps. It required a lot of digging through the API docs to figure this out.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top