What's the pros and cons of ForeignKey instead of ManyToManyField - django

In django tutorial it's modeled a poll structure:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
but I always wonder why they don't implement like ManyToMany relation:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
choices = models.ManyToManyField(Choice)
class Choice(models.Model):
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
What are the pros and cons?

I'm sure they used a ForeignKey (One To Many) relationship in the tutorial because they were trying to keep the example as simple as possible, with only two models: Question and Choice. Notice that votes is a field of the Choice model, which makes it very simple to show the results of the poll.
If you were doing a poll where you had the same choices for every question (Strongly Agree, Somewhat Agree, etc.), then a ManyToMany relationship might be appropriate. But that makes things more complicated. Your proposed models for a ManyToMany relationship were:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
choices = models.ManyToManyField(Choice)
class Choice(models.Model):
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
But this wouldn't work correctly without some changes.votes is still a field of the Choice table, but now each choice applies to many questions. You would be able to see that the Strongly Agree choice got 38 votes, but you couldn't tell which questions the voters agreed with. To be able to tabulate the votes correctly, you would have to do something like
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
choices = models.ManyToManyField('Choice', through='QuestionChoices')
class Choice(models.Model):
choice_text = models.CharField(max_length=200)
class QuestionChoices(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
votes = models.IntegerField(default=0)
Now you can tabulate exactly how many people voted for each question-choice combination, but it's more complicated and not very suitable for a getting-started tutorial.
With the ManyToMany models that you proposed, Django would automatically create the QuestionChoices model behind the scenes, but in order to attach the votes field to it you have to do it explicitly yourself.
If you have a relationship that could be modeled as either OneToMany or ManyToMany, the pros and cons depend on your particular application. In general you want to use a model that most accurately reflects the real-life situation. But you also have to consider how you need to update and summarize the data, and try to arrive at the best compromise. In my experience there aren't many situations where the choice is difficult.

Related

Many foreign keys in a model, better way to do models?

I'm kind of new to django. While writing the models for an app I'm doing, I felt like I was doing something wrong because of all the foreign keys I was using for a single model (ticket model to be specific)
My thinking at the beginning was that I wanted each Ticket to keep the information on it's creator, what team this ticket is assigned to and the comments made on the ticket. And other information that don't need foreign keys. Am I doing this right ? I dont know why, but I feel like there is a better way.
class Team(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=2000)
members = models.ManyToManyField(User, through='Team_members')
def __str__(self):
return self.name
class Ticket(models.Model):
name = models.CharField(max_length=200)
creator = models.ForeignKey(User, on_delete=models.CASCADE)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
comments = models.ForeignKey(Comment, on_delete=models.CASCADE)
#worker = models.ForeignKey(User, on_delete=models.CASCADE) *to be finished
description = models.CharField(max_length=500)
status = models.BooleanField(default=False)
date_opened = models.DateTimeField('date opened')
date_closed = models.DateTimeField('date closed',null=True, blank=True)
def __str__(self):
return self.name
class Team_member:
team = models.ForeignKey(Team)
user = models.ForeignKey(User)
date = models.DateTimeField('date joined')
class Comment:
text = models.CharField(max_length=2000)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.text
Your question actually has very little (like, nothing?) to do with Django.
Please read about database normalization, it should answer your questions.
Regarding your models, I could notice the following:
Your models look quite good. Don't be afraid of foreign keys, they are the main reason you use a relational database :)
I assume, User might be a member of a Team (service?) or somebody who opens a ticket. If so: when you will have worker foreign key, you most likely won't need team in the Ticket model. It will be redundant, as worker would have a relation to Team.
Nitpicking: Team_member is not pythonic, a PEP8 compliant version would be TeamMember

Creating a variable field in model Django

I want to ask the user how many questions they want to ask; based on their response, I want to populate a model with those many fields. The way I am currently thinking about doing that is as follows:
from __future__ import unicode_literals
from django.db import models
class Interview(models.Model):
title = models.TextField()
description = models.TextField()
number_questions = models.IntegerField()
question_one = models.ForeignKey('Question', related_name='question_one')
question_two = models.ForeignKey('Question', related_name='question_two')
question_three = models.ForeignKey('Question', related_name='question_three')
question_four = models.ForeignKey('Question', related_name='question_four')
question_five = models.ForeignKey('Question', related_name='question_five')
class Question(models.Model):
question_description = models.TextField()
prep_time = models.IntegerField()
response_time = models.IntegerField()
I realize that this solution is inefficient because a) the user is limited to a preset number of questions and b) if the user specifies less than 5 questions, there are unnecessary question entries created. What is a better way to go about storing multiple questions?
Do the foreign key relation the other way round. That's how you model a many-to-one relation:
class Interview(models.Model):
title = models.TextField()
description = models.TextField()
#property
def number_questions(self):
return self.questions.count()
class Question(models.Model):
interview = models.ForeignKey(Interview, related_name='questions')
question_description = models.TextField()
prep_time = models.IntegerField()
response_time = models.IntegerField()
Now you can access an interview's question via:
interview.questions.all()
An Interview can now have any number of Questions.
Btw, the related_name of all the ForeignKeys in your original Interview model should have been 'interview' to make any semantic sense.

Defining django models in an alphabetic order

Can I define django models in an alphabetic order?
Models taken from django tutorial:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
I'd like for Choice to be defined before the Question. After simple reordering
class Choice(models.Models):
question = models.ForeignKey(Question)
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
class Question(models.Models):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
Fails, because I need Question before telling interpreter what it is.
Adding simple empty
class Choise(models.Model): pass
class Question(models.Model): pass
causes django to scream and still fail python manage.py makemigrations polls.
Is there a hack to define models in alphabetical order?
Or should I forfeit this idea and just define from totally unrelks ated to related to everything?
NOTE:
according to Sayse, the workarounds break IDEs in that IDE can't know whether you mean literal string or if you are looking for a class.
If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself:
question = models.ForeignKey('Question')
instead of
question = models.ForeignKey(Question)
From https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.ForeignKey
You can reference a model that has not been yet declared using quotes:
class Choice(models.Models):
question = models.ForeignKey('Question')
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
class Question(models.Models):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')

django voting app - relationship between different models

I have a simple voting app which has 2 models Poll and Choice as most voting apps do.
Poll model has the following fields:
question - charField
pub_date - date and time
end_date - date and time
Also, each Poll has 2 choices.
Choice model has the following fields:
Poll - ForeignKey
choice - ImageField
vote - Integer
I have another model Person. A Poll occurs between 2 people from the Person model.
Person Model:
Name - Charfield
age - charfield
image - imagefield
...
I want to accomplish the following in the admin;
Create Poll (this is a simple one)
Create Choice - take image from Person Model rather than uploading new.
if a choice is added against a poll, the choice and the votes on that choice automatically show up as read only fields on Poll.
Person model shows how many Polls has the Person taken part in and how many of them has he won and lost.
Points 2,3 and 4 are the ones I am struggling with. please keep your responses simple, I am a newbie.
class Poll(models.Model):
question = models.CharField(max_length=250)
pub_date = models.DateTimeField()
end_date = models.DateTimeField()
class Choice(models.Model):
Poll = models.ForeignKey(Poll)
choice = models.ImageField(upload_to="choice")
vote = models.IntegerField()
class Person(models.Model):
name = models.CharField(max_length=200)
age = models.IntegerField(blank=True, null=True)
image = models.ImageField(upload_to='img')
let me know if the question is not clear.
It is not totally clear to me what you want to achieve. Implying you want to answer the question in reference to two images I would use the following models:
class Poll(models.Model):
question = models.CharField(max_length=250)
person1 = models.ForeignKey(Person,related_name ="+") #"+" to block reverse reference
person2 = models.ForeignKey(Person,related_name ="+")
pub_date = models.DateTimeField()
end_date = models.DateTimeField()
class Choice(models.Model):
Poll = models.ForeignKey(Poll)
choice = models.ForeignKey(Person)
vote = models.IntegerField()
class Person(models.Model):
name = models.CharField(max_length=200)
age = models.IntegerField(blank=True, null=True)
image = models.ImageField(upload_to='img')
def votes_sum(self):
return len( self.choice_set.all()) #this returns all votes
In the admin you can then add the Choice model as Inline to the Poll.
class ChoiceInline(admin.TabularInline):
model = Choice
extra=1
class ProjectAdmin(admin.ModelAdmin):
inlines = [ChoiceInline]
class VotesAdmin(admin.ModelAdmin):
list_display = ['name', 'votes_sum']
readonly_fields = ('votes_sum',)
I did not test this code but you should get an idea how you can do this.

Working with multiple forms on a single page in Django

I'm new to Django and I'm having a hard time wrapping my head around how to deal with "nested" forms in a template and how to process those forms accordingly. I'm creating a polling application similar to the tutorial, but more complex. I have multiple models (Poll, Question, Choice, Vote). A poll contains many questions, a question contains many choices.
I want to allow a user to view all the questions in a poll at once and vote on each question by selecting a choice from each question's choice set. After the user selects a choice for each question, they submit all their votes at once and process them to create the vote objects.
I'm really scratching my head at how to do this. Any help would be greatly appreciated.
Here are how my models are set up:
models.py
class Poll(models.Model):
name = models.CharField(max_length=255, default="Unnamed Poll")
key = models.CharField(max_length=16, blank=True, editable=False, unique=True, db_index=True, null=True)
instructor = models.ForeignKey(User)
course = models.ForeignKey(Course)
active = models.BooleanField(default=False)
anonymous = models.BooleanField(default=True, help_text="Allow votes to be anonymous?")
class Question(models.Model):
question_text = models.CharField(max_length=255, verbose_name='Poll Question')
poll = models.ForeignKey(Poll)
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=255, verbose_name='Response Choice')
class Vote(models.Model):
question = models.ForeignKey(Question)
choice = models.ForeignKey(Choice)
student = models.ForeignKey(User)
You should use django.forms.Formsets in your case.Read about formsets here
Please comment to ask for further clarifications.
cheers :-)