Pranav
Pranav

Reputation: 447

How to display model's attributes in template implicitly (dynamically)

I'm making a highscore app for my game. This is my model

class Skills(models.Model):
    attack = models.IntegerField(default=1)
    attack_xp = models.FloatField(default=0)

    constitution = models.IntegerField(default=10)
    constitution_xp = models.FloatField(default=0)

    mining = models.IntegerField(default=1)
    mining_xp = models.FloatField(default=0)

    strength = models.IntegerField(default=1)
    strength_xp = models.FloatField(default=0)

    agility = models.IntegerField(default=1)
    agility_xp = models.FloatField(default=0)

    smithing = models.IntegerField(default=1)
    smithing_xp = models.FloatField(default=0)

    defence = models.IntegerField(default=1)
    defence_xp = models.FloatField(default=0)

    herblore = models.IntegerField(default=1)
    herblore_xp = models.FloatField(default=0)

    fishing = models.IntegerField(default=1)
    fishing_xp = models.FloatField(default=0)

    ranged = models.IntegerField(default=1)
    ranged_xp = models.FloatField(default=0)

    thieving = models.IntegerField(default=1)
    thieving_xp = models.FloatField(default=0)

    cooking = models.IntegerField(default=1)
    cooking_xp = models.FloatField(default=0)

    prayer = models.IntegerField(default=1)
    prayer_xp = models.FloatField(default=0)

    crafting = models.IntegerField(default=1)
    crafting_xp = models.FloatField(default=0)

    firemaking = models.IntegerField(default=1)
    firemaking_xp = models.FloatField(default=0)

    magic = models.IntegerField(default=1)
    magic_xp = models.FloatField(default=0)

    fletching = models.IntegerField(default=1)
    fletching_xp = models.FloatField(default=0)

    woodcutting = models.IntegerField(default=1)
    woodcutting_xp = models.FloatField(default=0)

    runecrafting = models.IntegerField(default=1)
    runecrafting_xp = models.FloatField(default=0)

    slayer = models.IntegerField(default=1)
    slayer_xp = models.FloatField(default=0)

    farming = models.IntegerField(default=1)
    farming_xp = models.FloatField(default=0)

    construction = models.IntegerField(default=1)
    construction_xp = models.FloatField(default=0)

    hunter = models.IntegerField(default=1)
    hunter_xp = models.FloatField(default=0)

    summoning = models.IntegerField(default=1)
    summoning_xp = models.FloatField(default=0)

    dungeoneering = models.IntegerField(default=1)
    dungeoneering_xp = models.FloatField(default=0)

    overall_xp = models.FloatField(default=0)
    overall = models.IntegerField(default=0)
    user_name = models.CharField(max_length=30, primary_key=True)

as you can see there are a lot of attributes. There are a total of 25 skill levels and experience points.

I want to display this on my template in the order specified in model class. Like this

attack_level | attack_exp
constitutionlvl | constitution_exp
mining_level | mining_exp

in a table.

I know I could do it explicitly but I don't want to do that.


This is what I've tried so far

I tried executing a raw query like this

cursor.execute("SELECT * FROM highscores_skills WHERE user_name = %s", [user_name])

so that I can get the data in a list and then loop it on template but it orders the data by column name. attack -> constitution -> construction -> crafting..etc like that.

If I do not use asterisk and use column names instead then I get what I want but there's one more problem.

This is my template code

{% for skill in skills %}
    <tr>
        <td class = "col1 align">
            <a href="">{{ skill }}</a>
        </td>
        <td class = "col2">
            <a href="">{{ player|lookup:forloop.counter0 }}</a>
        </td>
        <td class = "col3 align">
            <a href="">{{ player|lookup:forloop.counter }}</a>
        </td>
    </tr>
{% endfor %}

The loop runs 25 times. skills is an array of skill names. I made a custom filter to get list data by index using loop counter. This thing will work if I manage to +1 the value of loop counter in the end.

Because this is how it looks right now:

enter image description here

Upvotes: 1

Views: 216

Answers (1)

aumo
aumo

Reputation: 5554

A good solution would be to create two Model field subclasses, one for storing skill level and one for storing skill XP. Which would give something like:

from django.db import models


class XpField(models.FloatField):
    '''
    Django model field used to store a skill XP.
    '''
    def __init__(self, *args, **kwargs):
        # Have a default "default" set to 0.
        if kwargs.get('default') is None:
            kwargs['default'] = 0

        super(XpField, self).__init__(*args, **kwargs)


class LevelField(models.IntegerField):
    '''
    Django model field used to store a skill level.
    Takes a required argument xp_field which is the XpField
    associated with this field's skill.
    '''
    def __init__(self, *args, **kwargs):
        # Have a default "default" set to 1.
        if kwargs.get('default') is None:
            kwargs['default'] = 1
        self.xp_field = kwargs.pop('xp_field')

        super(LevelField, self).__init__(*args, **kwargs)

As you can see the LevelField stores a reference to a XpField instance which should be the field representing XP for the same skill.

You can then setup your Skills model by using those field classes:

class Skills(models.Model):
    attack_xp = XpField()
    attack = LevelField(xp_field=attack_xp)

    # etc...

    def get_skills(self):
        '''
        Returns a list of dictionnaries containing name, level and XP
        for each of the model's skills.
        '''
        skill_values = []

        # Iterate over all the model's fields.
        for field in self._meta.get_fields():
            if isinstance(field, LevelField):
                level = getattr(self, field.name)
                xp = getattr(self, field.xp_field.name)
                skill_values.append({
                    'name': field.name,
                    'level': level,
                    'xp': xp,
                })

        return skill_values

The get_skills method will iterate over the Skills model fields, detect the ones that are LevelField instances and use them to build a list of skills' data. The Model._meta.get_fields method only appeared in Django 1.8. If your are using an older version, I think Model._meta.fields will do the trick.

You can then use it in your template this way:

{% for skill in skills.get_skills %}
    <tr>
        <td class = "col1 align">
            <a href="">{{ skill.name }}</a>
        </td>
        <td class = "col2">
            <a href="">{{ skill.level }}</a>
        </td>
        <td class = "col3 align">
            <a href="">{{ skill.xp }}</a>
        </td>
    </tr>
{% endfor %}

P.S: I did not test the code so it may contain errors but I hope this gives you an idea on how to solve your issue.

Upvotes: 1

Related Questions