django: set model fields as choices in models.py - python

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.

Related

Django models default value based on parent length

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.

How to structure models for forecasting app?

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)

How to reduce quantity of an item in main table when it is being used in another table - django

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']

Calculate a number in a Django model from another model's data

I want to take data (amount_spent) from the field of each user and add those numbers up and display them in another field (total_revenue) from a different model (RevenueInfo).
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.db import models
from django import forms, views
# Create your models here.
#LoginInfo is being used, LoginForms in forms.py is
class LoginInfo(models.Model):
username = models.CharField('', max_length=10)
password = models.CharField('', max_length=15)
class ExtendedProfile(models.Model):
user = models.OneToOneField(User)
amount_spent = models.DecimalField(max_digits=6, decimal_places=2)
class RevenueInfo(models.Model):
total_amount_spent = models.DecimalField(max_digits=6, decimal_places=2, default=0)
total_revenue = models.ForeignKey(ExtendedProfile, null=True)
class Product(models.Model):
category = models.CharField(max_length=100)
name = models.CharField(max_length=100)
description = models.TextField()
#photo = models.ImageField()
price_CAD = models.DecimalField(max_digits=6, decimal_places=2)
quantity = models.DecimalField(max_digits=2, decimal_places=0, null=True)
How could I go about this? Would I iterate of each Usermodel and find User.amount_spent then add that to RevenueInfo.total_revenue? I'm not sure how to put that into code. Also I'm pretty sure I don't need both total_amount_spent and total_revenue but I feel like I need a ForeignKey
You could add a classmethod to the ExtendedProfile model to aggregate the amount_spent value for each User (which bypasses the need for a separate RevenueInfo model):
from django.db.models import Sum
class ExtendedProfile(models.Model):
....
#classmethod
def total_user_spend(cls):
return cls.objects.aggregate(total=Sum('amount_spent'))
Then you can get the total spend using ExtendedProfile.total_user_spend():
>>> ExtendedProfile.total_user_spend()
{'total': Decimal('1234.00')}
Yes, you can write a method for that in your model. There are 2 ways for it.
1) Writing a method that calculates the values and sets it to a instance value.
2) Writing a method that calculates the value and directly returns it.
For example purpose, here is the code for 2nd type.
# models.py
def total_amount_spent(self):
temp_values = [int(user.amount_spent) for user in ExtendedProfile.objects.all()]
return sum(temp_values)
And for using that value in views , but remeber it would be an integer by default
#views.py
value = RevenueInfo.total_amount_spent()
Avoid iterating over database entities in python (it can get really slow). Look into aggregation, it allows you to efficiently get sum (average, max, min, etc...) of values in a database:
>>> from django.db.models import Sum
>>> ExtendedProfile.objects.all().aggregate(Sum('amount_spent'))
{'amount_spent__sum': Decimal('1234.56')}
>>> # ... do whatever you want with the return value

Auto Incrementing natural keys with django / postgres

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.

Categories

Resources