2
\$\begingroup\$

This is the current iteration of my first model of a Person. The model is populated by a survey application using forms.ModelForm and a SessionWizardView which splits the survey over multiple pages

Would anybody be able to tell me if I am making any obvious mistakes? Any advice or efficiencies are much appreciated. I am completely self thought is even very obvious things to most developers might not be very obvious to me.

models.py

from django import forms 

from django.forms import ModelForm
from django.db import models
import datetime
from django.core.exceptions import ValidationError
from multiselectfield import MultiSelectField



class Person(models.Model):

    # These take the value of a jQuery Slider Bar
    instruction_task_one_value = models.SmallIntegerField(null=True, blank=True, max_length=1000)
    instruction_task_two_value = models.SmallIntegerField(null=True, blank=True, max_length=1000)

    start = models.CharField(null=True, max_length=100, blank=True)  

    spike_one_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 

    slider_one_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    slider_two_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    slider_three_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 

    DV_one_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    DV_two_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    DV_three_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 

    slider_four_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    slider_five_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    slider_six_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 

    DV_four_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    DV_five_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    DV_six_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 

    spike_two_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 

    slider_seven_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    slider_eight_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    slider_nine_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 

    DV_seven_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    DV_eight_value = models.SmallIntegerField(null=True, max_length=100, blank=True) 
    DV_nine_value = models.SmallIntegerField(null=True, max_length=100, blank=True)     




    sender = models.EmailField(null=True, blank=True, verbose_name='What is your email address?')     

    birthdate = models.DateField(null=True, blank=True) #overwritten in forms.py so passed no more arguments

    SEX = (
        ('MALE', 'Male'),
        ('FEMALE', 'Female'))    
    sex = models.CharField(null=True, blank=True, max_length=100, choices=SEX, verbose_name='What sex are you?')


    RELATIONSHIP = (
        ('SINGLE', "Single"),
        ('INARELATIONSHIP', "In a relationship"),
        ('MARRIED', "Married"),
        ('DIVORCED', "Divorced"),
        ('SEPARATED', "Separated"),
        ('WIDOWED', "Widowed"),)    
    relationship = models.CharField(null=True, blank=True, max_length=100, choices=RELATIONSHIP, verbose_name='What is your relationship status?')             


    STATE = (
        ('ALABAMA', 'Alabama'),
        ('ALASKA', 'Alaska'),
        ('ARIZONA', 'Arizona'),
        ('ARKANSAS', 'Arkansas'),
        ('CALIFORNIA', 'California'),
        ('COLORADO', 'Colorado'),
        ('CONNECTICUT', 'Connecticut'),
        ('DELAWARE', 'Delaware'),
        ('FLORIDA', 'Florida'),
        ('GEORGIA', 'Georgia'),
        ('HAWAII', 'Hawaii'),
        ('IDAHO', 'Idaho'),
        ('ILLINOIS', 'Illinois'),
        ('INDIANA', 'Indiana'),
        ('IOWA', 'Iowa'),
        ('KANSAS', 'Kansas'),
        ('KENTUCKY', 'Kentucky'),
        ('LOUISIANA', 'Louisiana'),
        ('MAINE', 'Maine'),
        ('MARYLAND', 'Maryland'),
        ('MASSACHUSETTS', 'Massachusetts'),
        ('MICHIGAN', 'Michigan'),
        ('MINNESOTA', 'Minnesota'),
        ('MISSISSIPPI', 'Mississippi'),
        ('MISSOURI', 'Missouri'),
        ('MONTANA', 'Montana'),
        ('NEBRASKA', 'Nebraska'),
        ('NEVADA', 'Nevada'),
        ('NEW_HAMPSHIRE', 'New Hampshire'),
        ('NEW_JERSEY', 'New Jersey'),
        ('NEW_MEXICO', 'New Mexico'),
        ('NEW_YORK', 'New York'),
        ('NORTH_CAROLINA', 'North Carolina'),
        ('NORTH_DAKOTA', 'North Dakota'),
        ('OHIO', 'Ohio'),
        ('OKLAHOMA', 'Oklahoma'),
        ('OREGON', 'Oregon'),
        ('PENNSYLVANIA', 'Pennsylvania'),
        ('RHODE_ISLAND', 'Rhode Island'),
        ('SOUTH_CAROLINA', 'South Carolina'),
        ('SOUTH_DAKOTA', 'South Dakota'),
        ('TENNESSEE', 'Tennessee'),
        ('TEXAS', 'Texas'),
        ('UTAH', 'Utah'),
        ('VERMONT', 'Vermont'),
        ('VIRGINIA', 'Virginia'),
        ('WASHINGTON', 'Washington'),
        ('WEST_VIRGINIA', 'West Virginia'),
        ('WISCONSIN', 'Wisconsin'),
        ('WYOMING', 'Wyoming'),
        ('DISTRICT_OF_COLUMBIA', 'District of Columbia'),
        ('PUERTO_RICO', 'Puerto Rico'),
        ('GUAM', 'Guam'),
        ('AMERICAN_SAMOA', 'American Samoa'),
        ('US_VIRGIN_ISLANDS', 'US Virgin Islands'),
        ('NORTHERN_MARIANO_ISLANDS', 'Northern Mariana Islands'),)           
    state = models.CharField(null=True, blank=True, max_length=100, choices=STATE, verbose_name='What State are you from?')


    INTERNET_USAGE = (
        ('INTERNET_LESS_THAN_ONE_HOUR_A_DAY', 'Less than one hour a day'),
        ('INTERNET_ONE_TO_TWO_HOURS_A_DAY', '1 - 2 hours a day'),
        ('INTERNET_TWO_TO_FOUR_HOURS_A_DAY', '2 - 4 hours a day'),
        ('INTERNET_FOUR_TO_SIX_HOURS_A_DAY', '4 - 6 hours a Day'),
        ('INTERNET_SIX_TO_EIGHT_HOURS_A_DAY', '6 - 8 hours a day'),
        ('INTERNET_EIGHT_PLUS_HOURS_A_DAY', '8 + hours a day'), )
    internet_usage = models.CharField(null=True, blank=True, max_length=100, default=None, choices=INTERNET_USAGE)


    SMART_PHONE_OWNERSHIP = (
        ('YES_SMARTPHONE', 'Yes'),
        ('NO_SMARTPHONE', 'No'),)    
    smart_phone_ownership = models.CharField(null=True, max_length=100, blank=True, choices=SMART_PHONE_OWNERSHIP, verbose_name='Do you own a Smartphone?')


    SMART_PHONE_USAGE = (
        ('SMART_PHONE_LESS_THAN_ONE_HOUR_A_DAY', 'Less than one hour a day'),
        ('SMART_PHONE_ONE_TO_TWO_HOURS_A_DAY', '1 - 2 Hours a day'),
        ('SMART_PHONE_TWO_TO_FOUR_HOURS_A_DAY', '2 - 4 hours a day'),
        ('SMART_PHONE_FOUR_TO_SIX_HOURS_A_DAY', '4 - 6 hours a Day'),
        ('SMART_PHONE_SIX_TO_EIGHT_HOURS_A_DAY', '6 - 8 hours a day'),
        ('SMART_PHONE_EIGHT_PLUS_HOURS_A_DAY', '8 + hours a day'),)    
    smart_phone_usage = models.CharField(null=True, max_length=100, default=None, choices=SMART_PHONE_USAGE, verbose_name='If Yes, How many hours a day do you access the Internet on it?')



    EDUCATION = (
        ('ELEMENTARY_SCHOOL_ONLY', 'Elementary school only'),
        ('SOME_HIGH_SCHOOL_BUT_DID_NOT_FINISH', 'Some high school, but did not finish'),
        ('COMPLETED_HIGH_SCHOOL', 'Completed high school'),
        ('SOME_COLLEGE_BUT_DID_NOT_FINISH', 'Some college, but did not finish'),
        ('TWO_YEAR_COLLEGE_DEGREE', 'Two-year college degree / A.A / A.S.'),
        ('FOUR_YEAR_COLLEGE_DEGREE_BA_BS', 'Four-year college degree / B.A. / B.S.'),
        ('SOME_GRADUATE_STUDY', 'Some graduate study'),
        ('COMPLEATED_MASTERS_OR_PROFESSIONAL_DEGREE', 'Completed Masters or professional degree'),
        ('ADVANCED_GRADUATE_STUDY_OR_PHD', 'Advanced Graduate study or Ph.D.'),
        ('OTHR', 'Other'), )
    education = models.CharField(null=True, max_length=100, default=None, choices=EDUCATION, verbose_name='What is the highest level of education you have completed?')


    WAGES = (   
        ('UNDER_S10000', 'Under $10,000'),
        ('S10000_S19999', '$10,000 - $19,999'),
        ('S20000_S29999', '$20,000 - $29,999'),
        ('S30000_S39999', '$30,000 - $39,999'),
        ('S40000_S49999', '$40,000 - $49,999'),
        ('S50000_S59999', '$50,000 - $59,999'),
        ('S60000_S69000', '$60,000 - $69,999'),
        ('S70000_S79000', '$70,000 - $79,999'),
        ('S80000_S89999', '$80,000 - $89,999'),
        ('S90000_S99000', '$90,000 - $99,999'), 
        ('OVER_100000', 'Over $100,000'),    )
    wages = models.CharField(null=True, max_length=100, default=None, choices=WAGES, verbose_name='What is your expected income range for 2014?')


    PRESENTAGE_SAVINGS = ( 
        ('ZERO', '0%'),
        ('ZERO_FIVE', '0% - 5%'),
        ('FIVE_TEN', '5% - 10%'),
        ('TEN_FIFTEEN', '10% - 15%'),
        ('FIFTEEN_TWENTY', '15% - 20%'),
        ('TWENTY_TWENTYFIVE', '20% - 25%'),
        ('TWENTYFIVE_THIRTY', '25% - 30%'),
        ('THIRTY_THIRTYFIVE', '30% - 35%'),
        ('THIRTYFIVE_FORTY', '35% - 40%'),
        ('FORTY_FORTYFIVE', '40% - 45%'),
        ('FORTYFIVE_FIFTY', '45% - 50%'),
        ('FIFTY_PLUS', '50%+'),)
    presentage_savings = models.CharField(null=True, max_length=100, default=None, choices=PRESENTAGE_SAVINGS, verbose_name='What proportion of your weekly/monthly income do you save?')


    OCCUPATION = (   
        ('HOME_PARENT', 'Stay at home Parent'),
        ('UNEMPLOYED', 'Unemployed'),
        ('PROFESSIONAL', 'Professional'),
        ('SEMI_PROFESSIONAL', 'Semi-professional'),
        ('SKILLED_PROFESSIONAL', 'Skilled professional'),
        ('SKILLED_MANUAL', 'Skilled manual'),
        ('SEMI_SKILLED_MANUAL', 'Semi-skilled manual'),
        ('UNSKILLED_MANUAL', ' Unskilled manual'),
        ('OTHER', 'Other') )
    occupation = models.CharField(null=True, max_length=100, default=None, choices=OCCUPATION, verbose_name='Which best describes your current occupation?')


    LIVING = (
        ('RENT_ALONE', 'Rent alone'),
        ('REST_MULTIPLE_OCCUPANCY', 'Rent in multiple occupancy'),
        ('DORMITORY', 'Dormitory'),
        ('OWN_HOME_WITH_MORTGUAGE', 'Own home, with mortgage'),
        ('OWEN_HOME_NO_MORTGUAGE', 'Own home, no mortgage'),
        ('LIVING_WITH_FAMILY', 'Living with family'),
        ('NO_PERMANENT_LIVING_ADDRESS', 'No permanent living address'),
        ('OTHER', 'Other'),)
    living = models.CharField(null=True, max_length=100, default=None, choices = LIVING, verbose_name = 'What are your current living arrangements?')



    LIBERAL_CONSERVATIVE = (
        ('STRONG_LIBERAL', 'Strong Liberal'),
        ('NOT_SO_STRONG_LIBERAL', 'Not so strong Liberal'),
        ('INDEPENDENT_LEANING_LIBERAL', 'Independent leaning Liberal'),
        ('INDEPENDENT', 'Independent'),
        ('INDEPENDENT_LEANING_CONSERVATIVE', 'Independent leaning Conservative'),
        ('NOT_SO_STRONG_CONSERVATIVE', 'Not so strong Conservative'),
        ('STRONG_CONSERVATIVE', 'Strong Conservative'),
        ('OTHER', 'Other'),
        ('DONT_KNOW', "Don't know"), )
    liberal_conservative = models.CharField(null=True, max_length=100, default=None, choices = LIBERAL_CONSERVATIVE, verbose_name = 'What most closely describes your political viewpoint?')


    DEMOCRAT_REPUBLICAN = (
        ('STRONG_LIBERAL', 'Strong Democrat'),
        ('NOT_SO_STRONG_LIBERAL', 'Not so strong Democrat'),
        ('INDEPENDENT_LEANING_LIBERAL', 'Independent leaning Democrat'),
        ('INDEPENDENT', 'Independent'),
        ('INDEPENDENT_LEANING_CONSERVATIVE', 'Independent leaning Republican'),
        ('NOT_SO_STRONG_CONSERVATIVE', 'Not so strong Republican'),
        ('STRONG_CONSERVATIVE', 'Strong Republican'),
        ('OTHER', 'Other'),
        ('DONT_KNOW', "Don't know"),)
    democrat_republican = models.CharField(null=True, max_length=100, default=None, choices = DEMOCRAT_REPUBLICAN, verbose_name = 'What most clearly reflects your political leaning?')

    VOTING_RIGHTS = (
        ('EXTREAMLY_IMPORTANT', 'Extremely important'),
        ('VERY_IMPORTANT', 'Very important'),
        ('MODERATELY_IMPORTANT', 'Moderately Important'),
        ('SOMEWHAT_IMPORTANT', 'Somewhat important'),
        ('NOT_VERY_IMPORTANT', 'Not very important'),
        ('OF_NO_IMPORTANCE', 'Of no importance'),
        ('OTHER', 'Other'),
        ('DONT_KNOW', "Don't know"),)

    voting_rights = models.CharField(null=True, max_length=100, default=None, choices = VOTING_RIGHTS, verbose_name = 'How important do you feel it is to exercise your voting rights?')


    ELECTION_TYPE = (
        ('NONE', 'None'),
        ('LOCAL', 'Local elections'),
        ('STATE', 'State elections e.g. Governorship'),
        ('NATIONAL', 'National elections e.g. Congress and Senate'),
        ('PRESIDENTIAL', 'Presidential elections'),
        ('OTHER', 'Other'),
        ('DONT_KNOW', "Don't know"),)  
    election = MultiSelectField(null=True, max_length=100, choices=ELECTION_TYPE, verbose_name = 'Which elections do you regularly vote in or intend to vote in? Select all that apply.')


    NEWS_ACCESS = (  
        ('RADIO', 'Radio'),
        ('TV', 'TV'),
        ('NEWSPAPERS', 'Newspaper'), 
        ('ONLINE', 'Online'),
        ('NONE', 'None'),
        ('OTHER', 'Other'),)
    news_access = MultiSelectField( max_length=100, choices=NEWS_ACCESS, verbose_name = 'Which news mediums do you access most often? Select all that apply.')


    STATION_ACCESS = (
        ('NBC', 'NBC'),
        ('CBS', 'CBS'),
        ('ABC', 'ABC'),
        ('Fox', 'Fox'),
        ('The_CW', 'The CW'),
        ('PBS', 'PBS'),
        ('LOCAL', 'Local'),
        ('NONE', 'None'),
        ('OTHER', 'Other'),)
    station_access = MultiSelectField( max_length=100, choices=STATION_ACCESS, verbose_name = 'Which TV stations do you watch on a regular basis? Select all that apply.')       



    # clean function which checks if 'other' boxes are selected in the survey and requires user to fill out additional information
    def clean(self):
        if self.party_benefit == 'YES_OTHER' and not self.party_benefit_message:
            raise ValidationError({'party_benefit_message': ['Required when \"Other\" is checked']})
        if self.topics == 'OTHER' and not self.topics_message:
            raise ValidationError({'topics_message': ['Required when \"Other\" is checked']})         
        if self.deliberate == 'YES_DELIBERATE' and not self.deliberate_message:
            raise ValidationError({'deliberate_message': ['Required when \"Yes\" is checked']})         
        if self.elements_features == 'ELEMENTS_YES' and not self.elements_features_message:
            raise ValidationError({'elements_features_message': ['Required when \"Yes\" is checked']}) 
        if self.limit_elements_features == 'LIMIT_ELEMENTS_YES' and not self.limit_elements_features_message:
            raise ValidationError({'limit_elements_features_message': ['Required when \"Yes\" is checked']})         



    def __unicode__(self):
        return self
\$\endgroup\$
3
\$\begingroup\$

As Django code, this seems quite well done and straightforward. The biggest problem I see is with naming.


The Person model is a hodgepodge of unrelated fields. A few fields describe a person, a few other fields seem to be related to the survey, but the vast majority of fields are completely generic, like slider_seven_value.

It looks as if you want to reuse the same model for multiple unrelated future surveys. You will end up with a very dirty database, that is only usable with external documentation explaining which field is what, depending on the survey. I suggest to not speculate ahead, and plan a model dedicated to your current survey, and when you need another one, plan a different model. You will have one table per survey, with intuitive column names, and overall less problems and confusion.


Other examples of bad naming are in the choice constants. For example, "separated" and "widow" are hardly a "relationship". These are usually called " marital status "

Other times the survey questions help you find better names. For example, for the question 'What most closely describes your political viewpoint?', the answer is hardly a kind of "LIBERAL_CONSERVATIVE". How about " political leaning " ? Likewise, instead of answering the question 'What most clearly reflects your political leaning?' with a "DEMOCRAT_REPUBLICAN", how about " political leaning "?

\$\endgroup\$
  • \$\begingroup\$ Thanks for the input, I'm making some of the specific changes you suggested now. Question regarding "...a model dedicated to your current survey..." Are you suggesting that I change the name of e.g. slider_four_value to something like survey_name_slider_four_value ? Thanks \$\endgroup\$ – Deepend Jun 20 '15 at 13:21
3
\$\begingroup\$

sex field has standard https://en.wikipedia.org/wiki/ISO/IEC_5218 for it's numeric value.

RELATIONSHIP class property can make some troubles if future, when you'll need to make some manipulations with this list. But in this case it is acceptable solution.

state there is library called cities_light, it has states and some additional properties. Cons of using it - it require to download it's database (about 400MB) during migration.

SMART_PHONE_OWNERSHIP overuse of CharField property. You should use boolean field and then adopt data presentation on form.

Also, you probably wish to separate this huge model into few for performance improvements, but this require some profiling workaround.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.