Django: objects and model_set - python

I am learning django 1.10 official tutorial part 2
class Question(models.Model):
# ......
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
#.......
Recent i saw the following command:-
q = Question.objects.get(id=1)
q.choice_set.all()
My questions:-
How Question instance contain choice_set, i know it for accessing related objects.
Why is this not valid
c = Choice.objects.get(id=1)
c.question_set.all()

The Question model does not have an explicit reference to the Choice model; however, Django automatically adds a reverse-reference, which is by default called choice_set. You can override this by related_name keyword on the model such as:
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='choices')
Now you can reference all of the choices for a question like this:
q = Question.objects.get(pk=1)
q.choices.all()
To answer your second question, the reason you cannot use reference question_set.all() from a choice object is because for each choice, there is only one question plus there is an explicit reference to the question object. In other words the Choice model already has a field called Question, which points to the Question model.
Hope that helps.

Related

Django Model inheritance for efficient code

I have a Django app that uses an Abstract Base Class ('Answer') and creates different Answers depending on the answer_type required by the Question objects. (This project started life as the Polls tutorial). Question is now:
class Question(models.Model):
ANSWER_TYPE_CHOICES = (
('CH', 'Choice'),
('SA', 'Short Answer'),
('LA', 'Long Answer'),
('E3', 'Expert Judgement of Probabilities'),
('E4', 'Expert Judgment of Values'),
('BS', 'Brainstorms'),
('FB', 'Feedback'),
)
answer_type = models.CharField(max_length=2,
choices=ANSWER_TYPE_CHOICES,
default='SA')
question_text = models.CharField(max_length=200, default="enter a question here")
And Answer is:
class Answer(models.Model):
"""
Answer is an abstract base class which ensures that question and user are
always defined for every answer
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
class Meta:
abstract = True
ordering = ['user']
At the moment, I have a single method in Answer (overwriting get_or_update_answer()) with type-specific instructions to look in the right table and collect or create the right type of object.
#classmethod
def get_or_update_answer(self, user, question, submitted_value={}, pk_ans=None):
"""
this replaces get_or_update_answer with appropriate handling for all
different Answer types. This allows the views answer and page_view to get
or create answer objects for every question type calling this function.
"""
if question.answer_type == 'CH':
if not submitted_value:
# by default, select the top of a set of radio buttons
selected_choice = question.choice_set.first()
answer, _created = Vote.objects.get_or_create(
user=user,
question=question,
defaults={'choice': selected_choice})
else:
selected_choice = question.choice_set.get(pk=submitted_value)
answer = Vote.objects.get(user=user, question=question)
answer.choice = selected_choice
elif question.answer_type == 'SA':
if not submitted_value:
submitted_value = ""
answer, _created = Short_Answer.objects.get_or_create(
user=user,
question=question,
defaults={'short_answer': submitted_value})
else:
answer = Short_Answer.objects.get(
user=user,
question=question)
answer.short_answer = hashtag_cleaner(submitted_value['short_answer'])
etc... etc... (similar handling for five more types)
By putting all this logic in 'models.py', I can load user answers for a page_view for any number of questions with:
for question in page_question_list:
answers[question] = Answer.get_or_update_answer(user, question, submitted_value, pk_ans)
I believe there is a more Pythonic way to design this code - something that I haven't learned to use, but I'm not sure what. Something like interfaces, so that each object type can implement its own version of Answer.get_or_update_answer(), and Python will use the version appropriate for the object. This would make extending 'models.py' a lot neater.
I've revisited this problem recently, replaced one or two hundred lines of code with five or ten, and thought it might one day be useful to someone to find what I did here.
There are several elements to the problem I had - first, many answer types to be created, saved and retrieved when required; second, the GET vs POST dichotomy (and my idiosyncratic solution of always creating an answer, sending it to a form); third, some of the types have different logic (the Brainstorm can have multiple answers per user, the FeedBack does not even need a response - if it is created for a user, it has been presented.) These elements probably obscured some opportunity to remove repetition, which make the visitor pattern quite appropriate.
Solution for elements 1 & 2
A dictionary of question.answer_type codes that map to the relevant Answer sub-class, is created in views.py (because its hard to place it in models.py and resolve dependencies):
# views.py:
ANSWER_CLASS_DICT = {
'CH': Vote,
'SA': Short_Answer,
'LA': Long_Answer,
'E3': EJ_three_field,
'E4': EJ_four_field,
'BS': Brainstorm,
'FB': FB,}
Then I can get the class of Answer that I want 'get_or_created' for any question with:
ANSWER_CLASS_DICT[question.answer_type]
I pass it as a parameter to the class method:
# models.py:
def get_or_update_answer(self, user, question, Cls, submitted_value=None, pk_ans=None):
if not submitted_value:
answer, _created = Cls.objects.get_or_create(user=user, question=question)
elif isinstance(submitted_value, dict):
answer, _created = Cls.objects.get_or_create(user=user, question=question)
for key, value in submitted_value.items():
setattr(answer, key, value)
else:
pass
So the same six lines of code handles get_or_creating any Answer when submitted_value=None (GET) or not (submitted_value).
Solution for element 3
The solution for element three has been to extend the model to separate at least three types of handling for users revisiting the same question:
'S' - single, which allows them to record only one answer, revisit and amend the answer, but never to give two different answers.
'T' - tracked, which allows them to update their answer every time, but makes the history of what their answer was available (e.g. to researchers.)
'M' - multiple, which allows many answers to be submitted to a question.
Still bug-fixing after all these changes, so I won't post code.
Next feature: compound questions and question templates, so people can use the admin to screen to make their own answer types.
Based on what you've shown, you're most of the way to reimplementing the Visitor pattern, which is a pretty standard way of handling this sort of situation (you have a bunch of related subclasses, each needing its own handling logic, and want to iterate over instances of them and do something with each).
I'd suggest taking a look at how that pattern works, and perhaps implementing it more explicitly.

In Django-admin, how to show foreignkey's foreignkey in the same page?

I got a Paper and its foreignkey ChoiceQuestion model, and its foreignkey Choices model.
I'm wondering if django-admin show Choices in the change-form page of Paper.
class Paper(models.Model):
"""pagers"""
# ...
class ChoiceQuestion(models.Model):
paper = models.ForeignKey(Paper)
name = models.CharField(max_length=256)
class Choice(models.Model):
choice_question = models.ForeignKey(ChoiceQuestion)
text = models.CharField(max_length=256)
normally we can edit ChoiceQuestion in Paper's editing page, but how could I edit Choice in this page also?
Maybe I did not describe clearly.
but djang-nested-inline helps a lot.
This allows we edit foreignkey's foreignkey in same page and nested for deep level.

Django: Counting objects based on filter value that changes?

I have the following models:
class Question(models.Model):
question = models.CharField(max_length=100)
detail = models.TextField(null=True, blank=True)
class PossibleAnswers(models.Model):
question = models.ForeignKey(Question)
value = models.CharField(max_length=200)
class Answer(models.Model):
question = models.ForeignKey(Question)
value = models.CharField(max_length=200)
Each Question has PossibleAnswers defined defined by the User. For Example: Question - What is the best fruit? Possible Answers - Apple, Orange, Grapes. Now other user's can Answer the question with their responses restricted to PossibleAnswers.
The problem I'm having is getting a count of each Answer. How many people responded by selecting Apple vs Orange vs Grapes?
Question.answer_set.filter(value="Grapes").count() returns a count of all grape answers, but what if you don't know what the filter criteria (grapes in this case) will be? Since the user defines the answer options, and defines how many different options there are, how would you get a response count for each answer option?
First I would change your models. Your schema is not normalized. That means you keep the same information (the text of an answer) in multiple places. That is considered a bad thing by itself, but also makes designing the right query much harder. That is how I think your models should look like:
class Question(models.Model):
question = models.CharField(max_length=100)
detail = models.TextField(null=True, blank=True)
class PossibleAnswer(models.Model):
question = models.ForeignKey(Question)
value = models.CharField(max_length=200)
class Answer(models.Model):
possible_answer = models.ForeignKey(PossibleAnswers)
Every time a user vote for a possible answer you add a new Answer model referencing that possible answer. You can also add additional fields to the Answer model, like a Foreign Key referencing the user voting.
Then you would use the following code to get the information (i.e. how many actual answers there are for every possible answer) for a question foo:
PossibleAnswer.objects.filter(question=foo).annotate(Count('answer'))
You need an aggregation query:
from django.db.models import Count
Question.objects.annotate(Count('answer'))
Does this help you out?
q = Question.objects.get(pk=1)
[{v: q.answer_set.filter(value=v).count()}
for v in [pa.value for
pa in q.possibleanswers_set.all()]]
It will return a dictionary of {'possible_answer': 'count'} for each possible answer that exists for Question object q.
This is however not the most efficient way of doing things if you intend to use it on a larger scale (say for all Question objects), in which case you'd be much better off using aggregation (read about it here).

Where does <model>_set come from in my Django model?

I am going through the Django tutorial: https://docs.djangoproject.com/en/dev/intro/tutorial01/
And I am looking at the example of using the python shell with manage.py. Code snippet is copied from website:
# Give the Poll a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a poll's choices) which can be accessed via the API.
>>> p = Poll.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
>>> p.choice_set.all()
[]
This example is using a Poll model with a question and choice of answers, defined here:
class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField()
Now I don't understand where the object choice_set comes from. For a question we have a group of "Choices". But where is this explicitly defined? I just seems two classes are defined. Does the models.foreignKey(Poll) method connect the two classes (hence tables)?
Now where does the suffix "_set" come from in choice_set. Is it because we are implicitly defining a one-to-many relationship between the Poll and Choice tables, hence we have a "set" of choices?
choice_set is put there automatically by the Django ORM because you have a foreign key from Choice to Poll. This makes it easy to find all the Choices for a particular Poll object.
It is hence not explicitly defined anywhere.
You can set the name of the field with the related_name parameter to ForeignKey.
The relationship _set command - in this instance choice_set- is an API accessor for the relationship (i.e., a ForeignKey, OneToOneField, or ManyToManyField).
You can read more about Django relationships, the relationship API, and _set here
But where is this explicitly defined?
It isn't; this is Django magic.
I just seems two classes are defined. Does the models.foreignKey(Poll) method connect the two classes (hence tables)?
Correct.
Now where does the suffix "_set" come from in choice_set. Is it because we are implicitly defining a one-to-many relationship between the Poll and Choice tables, hence we have a "set" of choices?
Yes. It's just a default; you can set the name explicitly via the normal mechanism.

What is choice_set in this Django app tutorial?

There is this line in the Django tutorial, Writing your first Django app, part 1:
p.choice_set.create(choice='Not much', votes=0)
How is choice_set called into existence and what is it?
I suppose the choice part is the lowercase version of the model Choice used in the tutorial, but what is choice_set? Can you elaborate?
UPDATE: Based on Ben's answer, I located this documentation: Following relationships "backward".
You created a foreign key on Choice which relates each one to a Question.
So, each Choice explicitly has a question field, which you declared in the model.
Django's ORM follows the relationship backwards from Question too, automatically generating a field on each instance called foo_set where Foo is the model with a ForeignKey field to that model.
choice_set is a RelatedManager which can create querysets of Choice objects which relate to the Question instance, e.g. q.choice_set.all()
If you don't like the foo_set naming which Django chooses automatically, or if you have more than one foreign key to the same model and need to distinguish them, you can choose your own overriding name using the related_name argument to ForeignKey.
Two crucial questions are asked here. First: How is choice_set called into existence. Second: What is it?
For all new developers like me, Let me describe how I made it easy for me. Let me answer the second question first. "What is it", through these 3 words? Model Instance, Objects-set related to that instance, Related_manager.
Models.py 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, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
Instance:
q = Question.objects.get(pk=3)
# Here q is an instance of model class 'Question'.
c = Choice.objects.get(pk=32)
# Here c is an instance of model class 'Choice'.
'Model Instance' is a single 'ROW' of an entire 'TABLE' of your database
Here, the Question Model is used as a foreign key to the Choice Model. Therefore, all the objects-set related to instance q can be filtered by using:
q.choice_set.all()
Therefore, choice_set here is, all the choices related to the question, that has pk=3.
Now, the answer to the first question needs the third word Related Manager. Django documentation here:-
If a model has a ForeignKey, instances of the foreign-key model will
have access to a Manager that returns all instances of the first
model. By default, this Manager is named FOO_set, where FOO is the
source model name, lowercased. This Manager returns QuerySets, which
can be filtered and manipulated as described in the “Retrieving
objects” section above.
This word (choice_set) can be changed using the 'related_name' parameter in the Foreign_key.
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="choices")
For backward relationtionship through foreign key:
q.choice_set.all()
# If using related_name, then it is same as
q.choices.all()
# All the choices related to the instance q.
For forward relationship:
choice_qs = Choice.objects.all()
choice_qs.filter(question=q)
# Same result as above. All the choices related to instance q.

Categories