Django - ForeignKey Filter Choices - django

I'd like to filter the choices that a user can choose in my ForeignKey Field.
I basically have a ForeignKey for the subject of the Test and the actual topic of the Test. These topics come from a different model and are linked to a subject. Now I'd like to filter the choices to only include the topics that are linked to the currently selected subject. Is that possible and if so, how?
models.py
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True)
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
class Thema(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.CharField(max_length=50)
class Subject(models.Model):
teacher = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=20)
The Problem if I use this:
# thema model #staticmethod
def return_thema(subject):
themen = Thema.objects.filter(subject=subject)
return {'thema': themen}
#test model
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True,limit_choices_to=Thema.return_thema(subject))
Is that I get the Error:
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Meaning I can't get the objects of the Thema Model while the models are loading
EDIT (for Swift):
That seemed to resolve the error when trying to makemigrations, but I now get this error, when visiting the admin portal to create a new Test:
File "/Users/di/Code/Schule/GymnasiumApp/venv/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1404, in build_filter
arg, value = filter_expr
ValueError: too many values to unpack (expected 2)

I think what you are looking for ideally would be ForeignKey.limit_choices_to
Please see the docs:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to
You can limit the choices available at a model level, which is enforced throughout the django app, including forms automatically.
Edit because OP provided more information
Ok so I believe if you declare the thema field on the test model like so, it will solve the issue, and I will explain why after:
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True, limit_choices_to=Q('thema_set__subject_set'))
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
We are essentially telling Django to evaluate the relationship between the limited choices "lazily" I.e. when the form is loaded dynamically. Django forms will look at the limit_choices_to field argument and apply it to the available choices.
I'm not 100% about the relationship of your models so the Q(...) I added, might actually need to be Q('subject_set')

If you use django forms you can use the model choice field.
In your view you can set your queryset of this choicefield. Zo you can filter it.
fields['your model field'].queryset = yourmodel.objects.filter(your filter parameters)

I think there is also problem in save method also. Aren't you need to write the name of the model inside like
return super(<modelName>).save(*args, **kwargs)

Related

How do I get a value from a different model in django?

Im new to django and im trying to figure out how to set the value in a model = to a value in another model
from django.db import models
from django.contrib.auth.models import User
class Company(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
name = models.CharField(max_length=100, null=True, blank=True) # This is the value I want
_id = models.AutoField(primary_key=True, editable=False)
def __str__(self):
return self.name
class Product(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
companyName = # I want the name here
_id = models.AutoField(primary_key=True, editable=False)
def __str__(self):
return self.name
I've tried these but they dont work
companyName = Company.name
companyName = models.CharField(Company.name, max_length=100, null=True, blank=True)
Company.name will not work because Company is a class, a template for making many Company objects, each with it's own name, so which Company object, or instance, do you want?
You need to get the particular Company first, and then you can set the companyName of the Product, or, you can set it when you save the Product instance, assuming you have a Company instance in mind, since you set null=True, so obviously if company is False, then there is no companyName.
Assuming you have a Company instance already when you save the Product model you can do:
class Product(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
companyName = # I want the name here
_id = models.AutoField(primary_key=True, editable=False)
def save(self, *args, **kwargs):
if self.company:
self.companyName = self.company.name
super(Product, self).save(*args, **kwargs)
def __str__(self):
return self.name
But, the next question is why do you want to even have a field called companyName when you have access to the name field of the Company instance anyway. Try to get it by:
# Get the Product instance you want
product = Product.objects.get(...)
# Then you can get the associated `Company` name like this:
print(product.company.name)
Suggestion
Lately I've been seeing many questions with something like:
_id = models.AutoField(primary_key=True, editable=False)
Not sure why, since Django does that automatically.

Django: How to generate unique order id

This is how my model looks like. When ever user orders. The order id provided by django is simple. Its like 1,2,3 ..... 100.
class UserOrder(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='orders',
on_delete=models.CASCADE)
cart = models.ForeignKey(Cart, related_name="orders", on_delete=models.CASCADE, null=True,
blank=True)
date = models.DateTimeField(default=datetime.now)
total_price = models.IntegerField(null=True, blank=True)
note = models.TextField(null=True, blank=True)
cancel_reason = models.TextField(null=True, blank=True)
cancelled = models.BooleanField(default=False)
confirmed = models.BooleanField(default=False)
def __str__(self):
return self.user.username
Your question is not well defined but I think I understand your problem, you need a id (lookup field) that is not simple.
you can use uuid, and use this uuid for all the lookup in the viewsets, instead of the sequentially created id by django.
something like this lookup_field = 'uuid' in views.
import uuid
class UserOrder(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, unique=True, db_index=True, editable=False)
read more about uuids here https://docs.python.org/3/library/uuid.html#uuid.uuid4
try using the django-random-id-model
run pip install django-random-id-model
in your django models.py file import RandomIDModel from django-random-id-model
then add RandomIdModel to your model class, in case this is:
class UserOrder(RandomIdModel):
#remaining codes here

Is it possible in Django to call custom `QuerySet` method on reverse related objects lookup?

E.g. there are next models and custom QuerySet:
from django.db import models
class ActiveQuerySet(models.QuerySet):
def active(self):
'''returns only active objects'''
'''supposing here would be a lot
of more complicated code
that would be great to reuse
'''
return self.filter(is_active=True)
class Category(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, default=None)
class Product(models.Model):
name = models.CharField(max_length=128, blank=True, null=True)
is_active = models.BooleanField(default=True)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, related_name='products', related_query_name="product", blank=True, null=True, default=None)
objects = ActiveQuerySet.as_manager()
Could you tell me if there is a way to call the active() method like this:
category = Category.objects.first()
category.products.active()
instead of doing like this:
category.products.filter(is_active=True)
Or how to implement appropriately such behavior?
You have to add the custom queryset into the parent model, not the _set. You can change custom_products to any other word.
class ActiveQuerySet(models.QuerySet):
def active(self):
'''returns only active objects'''
'''supposing here would be a lot
of more complicated code
that would be great to reuse
'''
return self.products.filter(is_active=True)
class Category(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, default=None)
custom_products = ActiveQuerySet.as_manager()
category.custom_products.active()

Django: IntegrityError when migrating after adding foreign key field

I have three models as follows in a Django app named markets:
class Market(models.Model):
title = models.CharField(max_length=50, default="")
current_price = models.DecimalField(max_digits=5, decimal_places=2, default=0.50)
description = models.TextField(default="")
shares_yes = models.IntegerField(default=0)
shares_no = models.IntegerField(default=0)
b = models.IntegerField(default=100)
cost_function = models.IntegerField(default=0)
open = models.BooleanField(default=True)
def __str__(self):
return self.title[:50]
def get_absolute_url(self):
return reverse('market_detail', args=[str(self.id)])
class Price(models.Model):
market = models.ForeignKey(
Market,
on_delete=models.CASCADE,
related_name='prices',
default=None)
price = models.DecimalField(
max_digits=5,
decimal_places=2,
default=0.50)
price_date = models.DateTimeField(
default=now,
blank=True)
def __str__(self):
return str(self.price)
def get_absolute_url(self):
return reverse('market_list')
class Share(models.Model):
user = models.ForeignKey('users.CustomUser',
on_delete=models.CASCADE,
related_name='user_shares',
default=None)
market = models.ForeignKey(
Market,
on_delete=models.CASCADE,
related_name='market_shares',
default=None)
share = models.IntegerField(default=0)
transaction_date = models.DateTimeField(
default=now,
blank=True)
def __str__(self):
return str(self.share)
def get_absolute_url(self):
return reverse('market_list')
I would like to add the following foreign key field to the Price model:
user = models.ForeignKey('users.CustomUser',
on_delete=models.CASCADE,
related_name='user_prices',
default=None)
When I run makemigrations on markets, there's no issue. But when I try to actually migrate the database, I get the following error:
django.db.utils.IntegrityError: column "user_id" contains null values
Why is that? I had no issues adding a user field to the Share model, so am not clear on why I run into problems when also looking to add it to Price.
When I run makemigrations on markets, there's no issue. But when I try
to actually migrate the database, I get the following error:
django.db.utils.IntegrityError: column "user_id" contains null values
Why is that?
A ForeignKey is by default non-NULLable. But you specify a default=None. The migraiton thus aims to insert NULL for the existing records, and that will fail.
You can make your ForeignKey nullable with:
user = models.ForeignKey('users.CustomUser',
on_delete=models.CASCADE,
related_name='user_prices',
null=True,
default=None
)
You will need to remove (or alter) the migration file, and recreate a migration file.
Another way to resolving this is providing a CustomUser object to which you link the existing records, with that user.
Note: usually it is better to use get_user_model() [Django-doc] instead of providing the user model as a reference or string. If you later change your mind, you can alter the setting, and then all ForeignKeys will be remapped on the new user model.

Django inline formset with manytomany fields

Ive spent a fair bit of time searching on this subject without finding some real up to date answers. I'm trying to create a form that creates a db entry. The basic idea is this:
Many events can have many people
So, the struggle here is that the user needs to create an event where the user can select all the people that attend. Each person that attends though, has certain things that also needs to be tracked per event. See the model below:
models.py
from django.db import models
from django.contrib.auth.models import User[]
class PersonRole(models.Model):
role = models.CharField(max_length=20, choices=ROLE_CHOICES, unique=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.role
class PersonClass(models.Model):
name = models.CharField(max_length=100, choices=CLASS_CHOICES, unique=True)
color = models.CharField(max_length=6, choices=COLOR_CHOICES, unique=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Person(models.Model):
name = models.CharField(max_length=100, unique=True)
personclass = models.ForeignKey(PersonClass, on_delete=models.CASCADE, blank=True, null=True)
personrole = models.ForeignKey(PersonRole, on_delete=models.CASCADE, blank=True, null=True)
value = models.IntegerField(default=0)
reliability = models.IntegerField(default=0)
last_item = models.DateField(auto_now=False, blank=True, null=True)
last_event_attended = models.DateField(auto_now=False, blank=True, null=True)
last_manager_attended = models.DateField(auto_now=False, blank=True, null=True)
item_received = models.BooleanField(default=False)
note = models.TextField(null=True, blank=True)
core_attendee = models.BooleanField(default=False)
enabled = models.BooleanField(default=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Location(models.Model):
name = models.CharField(max_length=100, unique=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Boss(models.Model):
name = models.CharField(max_length=100, unique=True)
location = models.ForeignKey(Location, on_delete=models.CASCADE)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Raid(models.Model):
date = models.DateTimeField(auto_now_add=True)
boss = models.ForeignKey(Boss, on_delete=models.CASCADE, null=True, blank=True)
success = models.BooleanField()
attendees = models.ManyToManyField(Person)
created_by = models.ForeignKey(User,
related_name="raids", blank=True, null=True,
on_delete=models.SET_NULL)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return str(self.date)
I've started down the path of just trying to use the generic in-built create\update\delete views and ran into this:
ValueError: 'roster.Person' has no ForeignKey to 'roster.Raid'.
forms.py
class RaidGenericCreateModelForm(forms.ModelForm):
class Meta:
model = Person
exclude = ()
RaidPersonFormSet = inlineformset_factory(Raid, Person, fields=['name', 'personclass', 'personrole', 'item_received'], extra=1, can_delete=False)
views.py
class RaidCreate(CreateView):
model = Raid
template_name = 'roster/raid_create.html'
form_class = RaidGenericCreateModelForm
success_url = None
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
person_form = RaidPersonFormSet
return self.render_to_response(
self.get_context_data(form=form,
person_form=person_form
)
)
There are 9-year old posts that say you cannot use inlineformset_factory with many to many fields. So my question here is, what are my options? What is the best way to go about simply creating an Event (referred to as Raid in the model) and at the same time selecting the people from the roster (referred to as Person in the model) and changing the options those people have associated to them for that event?
As an example of what I am trying to accomplish here:
Event 1
-Person A (selected, item_received=True)
-Person B (selected, item_received=False)
-Person C (selected, item_received=False)
-Person D (not selected, item_received=False)
Event 2
-Person A (selected, item_received=False)
-Person B (not selected, item_received=False)
-Person C (selected, item_received=True)
-Person D (selected, item_received=False)
Where the list of persons is showing all persons and some of the persons fields from the Person model.
The alternate thing you can do is use DjangoRestFramework for this purpose.
Using rest you can first send persons data to frontend then in frontend you can create Event and add person details for each event,and in last post all that data using javascript.Try it,it will surely work.