In Django, I am looking for recommendations on how to structure models for a forecasting app that is similar to a polling or quiz app - but not quite.
Overview of requirements:
(1) A quiz will have multiple questions.
(2) Questions can take multiple forms - True or False, Multiple Choice with 3 options, Multiple choice with 4 options, etc.
(3) Users submit forecasts (aka answers) for each question in the form of probabilities with the constraint that the total probability is 100%. So, for question #1 with three options A-C a user might forecast A: 30%, B: 50%, C: 20%
(4) Each question has 1 correct answer.
[Questions are scored using Brier scoring, but that is not essential for this discussion.]
I am familiar with the Django tutorial polling app and have looked at multiple quiz apps, but none of them address my problem.
If I use the structure of the Django polling tutorial with the number of choices being indeterminate, then I can't figure out how organize a user's forecast to a question - since that forecast must have a probability for each choice and the probabilities must add up to 100%.
If I create multiple models of questions, like TrueFalseQuestion, MultipleChoice3OptionsQuestion, etc then my views and templates become unwieldy since I can't just set context to Question model.
I considered creating a parent class Question with abstract=True and then creating children classes like "class TFQuestion(Question):". But, again this makes my ability to use generic templates like ListView limited, since I now have many children classes.
To summarize, what makes my problem different from everything else that I can find is: Most quiz apps have a User provide a single choice among multiple choices, no matter how many choices. My app requires an answer (forecast) that consists of a probability for each choice option, along with the constraint that probabilities equal 100%.
************* ADDING MORE DETAIL BELOW *************
To simplify, lets assume that I want every question to have 3 multiple choice options. In this case, every user forecast will consist of a set of 3 probabilities and a comment.
First question: Is there a better way to structure the models below for the case of 3 answer options?
class Quiz(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
class Question(model.Model):
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
question_text = models.CharField(max_length=200)
choice1 = models.CharField(max_length=200)
choice2 = models.CharField(max_length=200)
choice3 = models.CharField(max_length=200)
correct_choice = models.IntegerField()
class Forecast(model.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.CharField(max_length=200)
prob1 = models.IntegerField()
prob2 = models.IntegerField()
prob3 = models.IntegerField()
If this is a good structure for questions with 3 options, then how would I extend this to my original requirements of having questions with 2, 3, 4, 5 options?
My issue is that if I use the structure in the Django tutorial polling app, where the number of choices is indeterminate - then my Forecast model above breaks since it has hard-coded 3 options.
Sounds like a structure like this would work for you (with true-false questions being modeled as 2-choice questions):
class Quiz(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
class Question(model.Model):
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
text = models.CharField(max_length=200)
correct_choice = models.ForeignKey('Choice')
class Choice(model.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
text = models.CharField(max_length=200)
class Forecast(model.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.CharField(max_length=200)
class Meta:
unique_together = (('question', 'user'),)
class ForecastChoice(model.Model):
forecast = models.ForeignKey(Forecast, on_delete=models.CASCADE)
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
probability = models.IntegerField()
class Meta:
unique_together = (('forecast', 'choice'),)
The constraints not modelable with unique_together are left as an exercise for the reader:
a Forecast must end up with as many ForecastChoices as there are Choices in the Question
a Forecast's ForecastChoices' probabilities must sum up to 100
You will probably also want an ordering field for Questions, if that matters here, as well as the usual metadata such as creation time for Forecasts.
EDIT As requested in the comments, an example of a ForecastForm that has dynamically created probability fields for each choice and a view to drive it. It's dry-coded, so there may be silly mistakes, but the idea is solid.
class ForecastForm(forms.ModelForm):
class Meta:
model = Forecast
fields = ('comment',) # tell Django to only create this field
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Build dynamic fields.
self.probability_fields = []
for choice in self.instance.question.choice_set.all():
name = f'probability_{choice.id}'
field = forms.IntegerField(
label=f'{choice.text} – probability',
min_value=0,
max_value=100,
required=True,
)
# Put the field in the form...
self.fields[name] = field
# ... and store the name + choice object for later validation use
self.probability_fields.append((name, choice))
def clean(self):
super().clean()
probability_sum = 0
for name, choice in self.probability_fields:
probability = self.cleaned_data[name]
probability_sum += probability
if probability_sum != 100:
raise forms.ValidationError(f'Probabilities sum up to {probability_sum}, not the expected 100.')
def save(self):
with transaction.atomic():
super().save() # will save the `self.instance` Forecast object with the comment
for name, choice in self.probability_fields:
ForecastChoice.objects.create(
forecast=self.instance,
choice=choice,
probability=self.cleaned_data[name],
)
# This is a slightly unorthodox updateview in that the form it's driving
# is not directly related to the model the underlying "detail view" is acquiring.
class QuestionForecastView(views.UpdateView):
model = Question
def get_form(self, form_class=None):
# Unsaved forecast initialized with the question and user context.
forecast = Forecast(
question=self.get_object(),
user=self.request.user,
)
kwargs = self.get_form_kwargs()
kwargs['instance'] = instance
return ForecastForm(**kwargs)
Related
I'm making an app that has multiple exams and multiple questions for each exam.
This is my current 'Question' model:
class Question(models.Model):
exam = models.ForeignKey(Exam, related_name='questions', on_delete=models.CASCADE)
question = models.TextField()
explanation = models.TextField(blank=True, null=True)
TOPICS = [
('NA', 'Not Available'),
('Algebra', 'Algebra'),
('Geometry', 'Geometry'),
('Trig', 'Trigonometry'),
('Calc', 'Calculus'),
('Chem', 'Chemistry'),
('Geology', 'Geology'),
('Physics', 'Physics'),
('Reading', 'Reading'),
('Writing', 'Writing'),
('Spelling', 'Spelling'),
('Comprehension', 'Reading Comprehension'),
]
topic = models.CharField(max_length=20, choices=TOPICS, default='NA')
order = models.IntegerField(default=0)
created = models.DateTimeField(auto_now_add=True)
attempts = models.IntegerField(default=0, editable=False)
correct_attempts = models.IntegerField(default=0, editable=False)
class Meta:
unique_together = ['exam', 'order']
def __str__(self):
return f'{self.exam} - Q{self.order}'
You can pretty much ignore all the fields except the 'order' field. This field shows what order the question will appear on the exam.
I would like for the default value of this order field to be the number of existing questions in the exam + 1.
For example, if my exam has two questions in it already, and I'm trying to add a third question, the order of this question will default to '3' unless I manually change it.
I know this doesn't work, but this solution would work similarly to this line of code:
default=Question.objects.filter(exam=self.exam).count() + 1
I'm inexperienced in creating functions for models in django so please let me know how I would do something like this, thanks!
I solved this by overriding the save() function with this:
def save(self, *args, **kwargs):
self.order = Question.objects.filter(exam=self.exam).count() + 1
super().save(*args, **kwargs) # Call the "real" save() method.
I'm building a child chore management app, and I've run into a blocker. Here's what I'm trying to do:
assign individual kids individual instances of various rules (i.e. having multiple instances of "wash dishes", so that one kid can complete their version without it being marked as complete for each kid), so I will be able to:
only display the rules that are assigned to each kid on their own page (i.e. kid 1 can have their instances of rules a, b, c displayed, while kid 2 has their instances of rules b, c, d displayed)
end goal is to be able to tally up the points each kid has earned
Here's what I have done so far:
displaying list of all kids
ability to create rules instances
displaying all rules instances with every child.
Here are my models:
class Rule(models.Model):
"""
Model representing a base class for rules. Weight field is how much you want the rule to be worth. The same rule can be applied to multiple kids, and a single kid can be assigned multiple rules.
"""
name = models.CharField(max_length=50, help_text='Enter rule', default=None)
weight = models.IntegerField(default=0)
description = models.TextField(max_length=250, help_text='Enter description of rule')
completed = models.BooleanField(default=False, help_text='Is this rule completed?')
def get_absolute_url(self):
"""
url to gain access to one Rule
"""
return reverse('rule-detail', args=[str(self.id)])
class RuleInstance(models.Model):
"""
Model representing instances of rules. Instances should be assigned to kids.
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this rule')
rule = models.ForeignKey(Rule, on_delete=models.SET_NULL, null=True)
class Kid(models.Model):
name = models.CharField(max_length=20, help_text='Enter kid name', default=None)
rules = models.ManyToManyField(RuleInstance, help_text='Select a rule to give to this kid')
# https://docs.djangoproject.com/en/3.0/topics/db/examples/many_to_many/
points = models.IntegerField(default=0)
class Meta:
ordering = ['name']
# methods
def __str__(self):
return f'Name: {self.name}\n Points total: {self.points} \n Rules assigned: {self.rules}'
def get_absolute_url(self):
"""
url to gain access to one Kid
"""
return reverse('kid-detail', args=[str(self.id)])
Here are the relevant views:
def index(request):
"""
view function for site homepage. Stuff below will be subject to change if I don't like how it looks
"""
kids_list = Kid.objects.all()
rules_list = Rule.objects.all()
rule_instance_list = RuleInstance.objects.all()
context = {
'kids_list' : kids_list,
'rules_list' : rules_list,
'rule_instance_list': rule_instance_list,
}
return render(request, 'index.html', context=context)
class KidDetailView(generic.DetailView):
model = Kid
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['rule_instance_list'] = RuleInstance.objects.all()
return context
How should I adjust my models or views to accomplish what I'm after? And is a many to many relationship between the Kid and RuleInstance model the right way to go about this?
Since each RuleInstance will be assigned to just one kid, it's better to have a one-to-many relationship.
Since each instance of the rule should be marked as "completed" separately, the completed field should go on the instance, not on the rule.
like this:
class RuleInstance(models.Model):
"""
Model representing instances of rules. Instances should be assigned to kids.
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this rule')
rule = models.ForeignKey(Rule, on_delete=models.SET_NULL, null=True)
kid = models.ForeignKey(Kid, related_name='rules')
completed = models.BooleanField(default=False, help_text='Is this rule completed?')
This will add an implicit field to the Kid model called rules.
When you need to create a new rule instance, you'll do something like:
RuleInstance.objects.create(rule=the_rule, kid=the_kid)
(You'll probably also want a "date" field on the RuleInstance, if chores are regular.)
I am creating my model in Django and I have a many to many relationship between supplies and van kits. The idea is that an "item" can belong to many "van kits" and a "van kit" can have many " items. I created an intermediary model that will hold the relationship, but I am struggling to figure out a way to relate the quantity in the van kit table to the quantity in the main supplies table. For example, if I wanted to mark an item in the van kit as damaged and reduce the quantity of that supply in the van kit, I would also want to reduce the total count of that supply in the main "supplies" table until it has been replenished. I am thinking that maybe I'll have to create a function in my views file to carry out that logic, but I wanted to know if it could be implemented in my model design instead to minimize chances of error. Here's my code:
class supplies(models.Model):
class Meta:
verbose_name_plural = "supplies"
# limit the user to selecting a pre-set category
choices = (
('CREW-GEAR','CREW-GEAR'),
('CONSUMABLE','CONSUMABLE'),
('BACK-COUNTRY','BACK-COUNTRY')
)
supplyName = models.CharField(max_length=30, blank=False) # if they go over the max length, we'll get a 500 error
category = models.CharField(max_length=20, choices = choices, blank=False)
quantity = models.PositiveSmallIntegerField(blank=False) # set up default
price = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) # inputting price is optional
def __str__(self):
return self.supplyName
class van_kit(models.Model):
supply_name = models.ManyToManyField(supplies, through='KitSupplies',through_fields=('vanKit','supplyName'), related_name="supplies")
van_kit_name = models.CharField(max_length=100)
vanName = models.ForeignKey(vans, on_delete=models.CASCADE)
def __str__(self):
return self.van_kit_name
class KitSupplies(models.Model):
supplyName = models.ForeignKey(supplies, on_delete=models.CASCADE)
vanKit = models.ForeignKey(van_kit, on_delete=models.CASCADE)
quantity = models.PositiveSmallIntegerField(blank=False)
def __str__(self):
return str(self.supplyName)
class Meta:
verbose_name_plural = 'Kit Supplies'
I am fairly new to django, I have to learn it for a class project so if my logic is flawed or if a better way to do it is obvious, please respectfully let me know. I'm open to new ways of doing it. Also, I've read through the documentation on using "through" and "through_fields" to work with the junction table, but I'm worried I may not be using it correctly. Thanks in advance.
One option would be to drop/remove the field quantity from your supplies model and just use a query to get the total quantity.
This would be a bit more expensive, as the query would need to be run each time you want to know the number, but on the other hand it simplifies your design as you don't need any update logic for the field supplies.quantity.
The query could look as simple as this:
>>> from django.db.models import Sum
>>> supplies_instance.kitsupplies_set.aggregate(Sum('quantity'))
{'quantity__sum': 1234}
You could even make it a property on the model for easy access:
class supplies(models.Model):
...
#property
def quantity(self):
data = self.kitsupplies_set.aggregate(Sum('quantity'))
return data['quantity__sum']
I followed suggestion from this question
But i need to name one field of query_set to date filed of another object
My models are
class Choice(models.Model):
question = models.ForeignKey(Question, related_name='choice', on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
My view
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.values('choiceTime__choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)
I need to count number of submission in particular dates
I don't know how to name choiceTime__choice_date that serializer recognizes field in query set
class ChoiceDateSerializer(serializers.ModelSerializer):
choiceTime__choice_date = serializers.DateTimeField()
total_votes = serializers.IntegerField()
class Meta:
model = Choice
fields = ('id', 'choice_text','total_votes','choiceTime__choice_date')
i receive
{
"choice_text": "ant tower",
"total_votes": 3,
"choiceTime__choice_date": "2017-04-20"
}
But i want to recieve
{
"choice_text": "ant tower",
"total_votes": 3,
"choice_date": "2017-04-20"
}
Tried different options with no success. Definitely i am missing the point.
For my purposes it is working, but i want to have well written API.
2 option change time submission model?
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
coutner = models.IntegerField(default=0)
Is 2 option considers to be better approach to my particular problem? Thanks!
You are receiving a json object, which you add its key value.
for vote_detail in data:
if vote_detail.choiceTime__choice_date:
vote_detail.choice_date=vote_detail.choiceTime__choice_date
then serialize and save, a quick solution.
You could also add to your model the name that you want to call it. That's closer to backend and maybe worth delving into.
from django.db.models import Count,F
If anybody finds this problem and this is easiest answer i came up to.
As it was suggested before passing to serializer change value using model package functions
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.all().annotate(choice_date=F('choiceTime__choice_date')).values('choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)
Is it possible to set the choices of a field from another table?
for example
class Initial_Exam(models.Model):
Question_category = models.CharField(max_length=20, choices = Job.Job_Position)
class Job(models.Model):
Job_Position = models.CharField(max_length=30, null=True)
something like that
To close this:
As commented above, instead of twisting my implementation, setting the foreign key for Initial_Exam and using __unicode__ on Job did the job
should look like this:
class Job(models.Model):
Job_Position = models.CharField(max_length=30, null=True)
def __unicode__(self):
return self.Job_Requirements
that would display the Job_Position as choices in the admin panel
I thank the community, really appreciate it
You can definitely tie to another model with a ForeignKey relationship, but if you've got a smaller number of choices, here's a pattern you might want to consider for smaller choice lists (I use it for fairly constant choice lists less than 10):
class FrequencyType(models.Model):
FREQ_MONTHLY = 1
FREQ_QUARTERLY = 3
FREQ_YEARLY = 12
FREQ_CHOICES = (
(FREQ_MONTHLY, 'Monthly'),
(FREQ_QUARTERLY, 'Quarterly'),
(FREQ_YEARLY, 'Yearly'),
)
frequency = models.SmallIntegerField(default=FREQ_MONTHLY, choices=FREQ_CHOICES)
or, another example:
class YesNoModel(models.Model):
NO = 0
YES = 1
YESNO_CHOICES = (
(NO, 'No'),
(YES, 'Yes'),
)
weight = models.IntegerField(default=NO, choices=YESNO_CHOICES)
Good luck.