chipperdrew
chipperdrew

Reputation: 403

Django - Dynamically creating field names in a model (fields not saving)

I am trying to implement a voting system that keeps track of votes for each type of user on my site. My plan was to create a Vote model that keeps track of Up votes and Total votes for each user type and calculates the percentage of Up votes.

Hardcoded that looks something like this:

class Eduuser(AbstractUser):
    TYPE_1 = 'TY1'
    TYPE_2 = 'TY2'
    ...

   USER_TYPE_CHOICES = (
       (TYPE_1, 'Type 1'),
       (TYPE_2, 'Type 2'),
       ...
   )
   user_type = models.CharField(max_length=3, choices=USER_TYPE_CHOICES)


class Vote(models.Model):

    a = models.IntegerField(
        default=0, name=getattr(Eduuser, 'TYPE_1')+'_up')
    b = models.IntegerField(
        default=0, name=getattr(Eduuser, 'TYPE_2')+'_up')
    ...

    c = models.IntegerField(
        default=0, name=getattr(Eduuser, 'TYPE_1')+'_votes')
    d = models.IntegerField(
        default=0, name=getattr(Eduuser, 'TYPE_2')+'_votes')
    ...

    def perc(self):
        perc_array = []
        for user_type in getattr(Eduuser, 'USER_TYPE_CHOICES'):
            up = float(getattr(self, user_type[0]+'_up')) #Prevent int division
            votes = getattr(self, user_type[0]+'_votes')
            if votes==0:
                perc_array.append(0)
            else:
                perc_array.append(round(up/votes, 3))
        return perc_array

Although I don't anticipate adding more types, I would like for the code to look cleaner. My best attempt at looping over the user types was:

class Eduuser(AbstractUser):
    ...

class Vote(models.Model):
    for user_type in getattr(Eduuser, 'USER_TYPE_CHOICES'):
        models.IntegerField(
            default=0, name=user_type[0]+'_up')
        models.IntegerField(
            default=0, name=user_type[0]+'_votes')

    def perc(self):
        ...

However this does not save the fields (I guess because of the lack of assignment operator). So a couple of quick questions:

1) Is there a way to save fields without explicitly assigning them a name? Or can I convert the string name into a variable (from other posts I've read, this seems like a bad idea)?

2) Am I even approaching this voting idea logically? Part of me feels like there is a far easier approach to keeping track of votes for multiple types of users.

Any help is appreciated! Thanks!

Upvotes: 1

Views: 444

Answers (2)

Matt
Matt

Reputation: 10322

django-model-utils can make this cleaner with it's Choices helper.

You could do a Vote model in the following way (untested):

from model_utils import Choices


class User(AbstractUser):
    USER_CHOICES = Choices(
        ('one', 'Type 1'),
        ('two', 'Type 2'),
    )

   user_type = models.CharField(max_length=10, choices=USER_CHOICES)


class Vote(models.Model):
    """
    A single vote on a `User`. Can be up or down.
    """
    VOTE_CHOICES = Choices(
        ('upvote'),
        ('downvote'),
    )

    user = models.ForeignKey(User)
    vote = models.CharField(max_length=10, choices=VOTE_CHOICES)

Example usage – get the number of positive votes for all “Type 1” Users:

# retrieve all of the votes
all_votes = Vote.objects.all()
all_votes_count = len(all_votes)

# now retrieve all of the votes for users of ‘Type 1’    
type_one_votes = all_votes.filter(user__user_type=User.USER_CHOICES.one)
type_one_votes_count = len(type_one_votes)

# …and now filter the positive votes for ‘Type 1’ users
type_one_positives = type_one_votes.filter(vote=Vote.VOTE_CHOICES.upvote)
type_one_positive_vote_count = len(type_one_positives)

# etc.

Upvotes: 1

Peter DeGlopper
Peter DeGlopper

Reputation: 37339

Django uses some metaclass behavior to create fields based on what you declare, so this is not wholly trivial. There are some undocumented calls you can use to dynamically add fields to your model class - see this post:

http://blog.jupo.org/2011/11/10/django-model-field-injection/

That said, I would recommend a simpler approach. Create a model to hold your possible user types, then use it as a foreign key in the votes table:

class UserType(models.Model):
    type_name = models.CharField()

class Vote(models.Model):
    user_type = models.ForeignKey(UserType)
    total = models.PositiveIntegerField()

Or track the individual votes and sum as needed, either recording the user who cast the vote or just the user's type at the time the vote was cast. Depending on what you want to do if a user changes classes after voting you might need to save the user's type anyway.

If you do just track the sums, you have to think more carefully about transaction issues - I'd say track the user and enforce a uniqueness constraint.

Upvotes: 0

Related Questions