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.
Related
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)
In the forms.py I have a short piece of code which loads the data, but only after I edit print("hello") in it.
The code as follows:
models.py:
class CreateAssignment(models.Model):
user = models.ForeignKey(User, editable=False, blank=True, null=True)
progress = models.CharField(max_length=254, editable=False, blank=True, null=True)
class SetAssignment(models.Model):
mechanic = models.ForeignKey(User, editable=False, blank=True, null=True)
assignment = models.IntegerField(blank=True, null=True)
The mechanic is a permission, plus this mechanic's ID will show in the url of the website, when you will try to set an assignment for this mechanic.
forms.py:
class SetAssignmentForm(forms.ModelForm):
ASSIGNMENT_CHOICES = ()
for item in CreateAssignment.objects.all():
if item.progress == 'Scheduling':
user = User.objects.get(id=item.user_id).username
ASSIGNMENT_CHOICES += (
(item.id, user + ' - ' + str(item.id)),
)
assignment = forms.ChoiceField(choices=ASSIGNMENT_CHOICES, help_text='This is the assignment that you want to apply to this mechanic.')
class Meta:
model = SetAssignment
fields = ('assignment', )
The user_id in this situation is the user that has been set in the CreateAssignment model.
Now the issue is:
The for loop in the SetAssignmentForm works, but it loads data after I put a print in it or when I remove the print from it. Which of course shouldn't really affect the code.
Is there something I'm overlooking? I've been programming in Python Django for 8 weeks now, so if this is a basic program failure, please refer me to a page, because I haven't been able to find any information on this issue.
Thanks for the help.
For the ones that want to know:
views.py:
#login_required
def set_assignment(request):
form = SetAssignmentForm()
id = request.GET.get('id')
user_results = User.objects.filter(pk=id).values()
return render(request, 'pages/set_assignment.html', {'form': form, 'user_results': user_results})
Gif so you can visually see what's happening:
https://drive.google.com/open?id=1u7gfdiS7KitQWNVuvQEEOFJ9wD3q9rY6
You must not write code like this at class level. Anything at that level is only executed once, at definition time - ie when the class is first imported.
If you need to make the values dynamic, you should put the logic inside the __init__ method:
class SetAssignmentForm(forms.ModelForm):
assignment = forms.ChoiceField(choices=[], help_text='This is the assignment that you want to apply to this mechanic.')
def __init__(self, *args, **kwargs):
super(SetAssignmentForm, self).__init__(*args, **kwargs)
items = CreateAssignment.objects.filter(progress='Scheduling').select_related('user')
choices = [(item.id, '{} - {}'.format(item.id, item.user.username)) for item in items]
self.fields['assignment'].choices = choices
(Note, your query logic was very inefficient; my code only hits the database one time.)
However, here you don't even need to do that, because Django already has a form field - ModelChoiceField - that takes its values from the database. You can use a custom subclass of that to show the representation:
class AssignmentField(forms.ModelChoiceField):
def label_from_instance(self, item):
return (item.id, '{} - {}'.format(item.id, item.user.username))
class SetAssignmentForm(forms.ModelForm):
assignment = forms.AssignmentField(queryset=CreateAssignment.objects.filter(progress='Scheduling').select_related('user'))
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'),
)
I'm trying to combine this answer and this one, with a bit of for looping.
On creating a character, I want to add all possible skills with a value of 0 but I'm getting confused on how to follow the above answers.
I have this mixin:
class CrossCharacterMixin(models.Model):
cross_character_types = models.Q(app_label='mage', model='mage')
content_type = models.ForeignKey(ContentType, limit_choices_to=cross_character_types,
null=True, blank=True)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
abstract = True
(eventually, the cross_character_types will be expanded)
And this model:
class CharacterSkillLink(Trait, CrossCharacterMixin):
PRIORITY_CHOICES = (
(1, 'Primary'), (2, 'Secondary'), (3, 'Tertiary')
)
skill = models.ForeignKey('SkillAbility')
priority = models.PositiveSmallIntegerField(
choices=PRIORITY_CHOICES, default=None)
speciality = models.CharField(max_length=200, null=True, blank=True)
def __str__(self):
spec_string = " (" + self.speciality + ")" if self.speciality else ""
return self.skill.skill.label + spec_string
What I've started writing is this, on the NWODCharacter model:
def save(self, *args, **kwargs):
if not self.pk:
character_skills_through = CharacterSkillLink.content_object.model
CharacterSkillLink.objects.bulk_create([
[character_skills_through(skill=SkillAbility(
skill), content_object=self) for skill in SkillAbility.Skills]
])
super(NWODCharacter, self).save(*args, **kwargs)
This doesn't work as I don't think I'm passing in the right objects.
Based on this answer though:
from django.db import models
class Users(models.Model):
pass
class Sample(models.Model):
users = models.ManyToManyField(Users)
Users().save()
Users().save()
# Access the through model directly
ThroughModel = Sample.users.through
users = Users.objects.filter(pk__in=[1,2])
sample_object = Sample()
sample_object.save()
ThroughModel.objects.bulk_create([
ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
])
In this situation, what is my ThroughModel? Is it CharacterSkillLink.content_object.model ?
How do I do this in my scenario? I'm sorry if this is trivial, but I'm struggling to get my head round it.
It looks to me like CharacterSkillLink itself is your through model in this case... it generically joins a content type to a SkillAbility
If you think about it, it also makes sense that if you're doing a bulk_create the objects that you pass in must be of the same model you're doing a bulk_create on.
So I think you want something like this:
def save(self, *args, **kwargs):
initialise_skill_links = not self.pk
super(NWODCharacter, self).save(*args, **kwargs)
if initialise_skill_links:
CharacterSkillLink.objects.bulk_create([
CharacterSkillLink(
skill=SkillAbility.objects.get_or_create(skill=skill)[0],
content_object=self
)
for skill in SkillAbility.Skills
])
Note you had too many pairs of [] inside your bulk_create.
Also I think you should use SkillAbility.objects.get_or_create()... for a foreign key you need the related object to exist. Just doing SkillAbility() won't fetch it from the db if it already exists and won't save it to the db if it doesn't.
Let me preface this in saying that I'm a UI dev who's trying to branch out into more backend coding, so excuse me if my verbiage is off at all. This is could be a duplicate, but i'm not sure what on god's good green earth i'm even supposed to call what i want to do.
Basically, I have categories, and images. I need to label each image with an acronym of the category it belongs to, and increment a sku after.
For Example, the following images would be automatically labeled like...
ABC-1
ABC-2
DEF-1
DEF-2
DEF-3
ABC-3*
*note: I want it to increment the ID based on the category, not the total # of images
How would I achieve this in idiomatic Django?
Models:
class Group(models.Model):
title = models.CharField(max_length=200)
abbv = models.CharField(max_length=200)
urlified = models.CharField(max_length=200)
description = models.TextField(blank=True)
hidden = models.BooleanField()
def __unicode__(self):
return self.title
class Photo(models.Model):
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
class Meta:
ordering = ('pub_date',)
If you want true composed primary keys, you might want to use django-compositepks, but that is not ideal. You might be better off breaking DRY and recording the number (see the category_auto_key field and default).
Transactions will solve it this way:
from django.db import transaction
class Group(models.model):
# your fields
img_count = models.IntegerField()
#transaction.atomic
def next_sku(self):
self.img_count += 1
self.save()
return self.img_count
class Photo(models.Model):
# your fields
category_auto_key = models.IntegerField(editable=False)
def category_image(self):
return self.group.abbv+"-"+str(self.category_auto_key)
def save(self, *args, **kwargs):
if not self.category_auto_key:
self.category_auto_key = self.group.next_sku()
super(Photo, self).save(*args, **kwargs)
When you need this in your templates, just enclose it in double brackets:
{{ photo.category_image }}
I'm curious if you just want to generate and store the acronym and sku in a text field, or if you are trying to create relationships between your image categories?
If the later, I would look for a different approach.
If the former, i would use a customized set or save method (hook?) for your image model. It will need do a small one time lookup to count the number of acronym already existing, but I wouldn't worry about the performance too much.
Wasn't sure how to do this exactly in Django off the top of my head, but it looks like the accepted answer works similarly. Anyways, here is my attempt at setting a Model Field during save. Be warned this in untested.
After looking into it more I think that Beltiras' solution is better
class Photo(models.Model):
# simple column definitions
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
# new column for storing abbv sku
category_label = models.CharField(max_length=200)
# save override
def save(self, *args, **kwargs):
# hopefully only set category_label on first save, not sure this
# works, open to other ideas
if (self.pk is None):
count = Photo.objects.filter(group=self.group).count()
label = self.group.abbv + '-' + count
setattr(self, 'category_label', label)
# call the super class' save method
super(Photo, self).save(*args, ** kwargs)
The part I am least sure about is:
count = Photo.objects.filter(group=self.group).count()
The idea is to query the photos table for photos in the same group and count them. This may need to be replaced with a direct SQL call or done some other way. Let me know what you find.