Django queryset that returns all unassigned fks - python

I have 2 models with a 1-1 relation (essentially a resource pool). For the example code, I will simply use nuts and bolts. There will be many more nuts (available resources) than bolts (which will each require 1 nut). However, if a nut can only be assigned to one bolt.
The constraint is easy enough to set up with the unique=True named param to the ForeignKey method.
The problem arises from the ModelForm. When rendered, the form will contain every nut in the dropdown. I would like to restrict it to only show nuts that haven't already been claimed by a bolt.
I am aware of the fields attribute of the ModelForm class, but am unable to come up with a query set filter that adequately addresses the issue. Here is example code of my problem:
from django.db import models
from django.forms import ModelForm
# Create your models here.
class Nut(models.Model):
size = models.CharField()
class Bolt(models.Model):
size = models.CharField()
nut = models.ForeignKey( Nut, unique=True )
class BoltForm(ModelForm):
def __init__(self, *args, **kwargs):
super(BoltForm, self).__init__(*args, **kwargs)
self.fields['nut'].queryset = # All unassigned nuts

Try this:
self.fields['nut'].queryset = Nut.objects.exclude(
pk__in=Bolt.objects.values('nut').query)
Update:
Of three expressions generating the same sql query:
pk__in=Bolt.objects.values('nut')
pk__in=Bolt.objects.values_list('nut')
pk__in=Bolt.objects.values('nut').query
I'd choose the last one as most straight-forward (although in other two cases the list and dict aren't created in fact: django 'understands' the intention without explicit mentioning of .query)
Also, consider Daniel Roseman's answer. It is another approach to do the same thing.

Nut.objects.filter(bolt=None)

Related

Django annotate value based on another model field

I have these two models, Cases and Specialties, just like this:
class Case(models.Model):
...
judge = models.CharField()
....
class Specialty(models.Model):
name = models.CharField()
sys_num = models.IntegerField()
I know this sounds like a really weird structure but try to bare with me:
The field judge in the Case model refer to a Specialty instance sys_num value (judge is a charfield but it will always carries an integer) (each Specialty instance has a unique sys_num). So I can get the Specialty name related to a specific Case instance using something like this:
my_pk = #some number here...
my_case_judge = Case.objects.get(pk=my_pk).judge
my_specialty_name = Specialty.objects.get(sys_num=my_case_judge)
I know this sounds really weird but I can't change the underlying schemma of the tables, just work around it with sql and Django's orm.
My problem is: I want to annotate the Specialty names in a queryset of Cases that have already called values().
I only managed to get it working using Case and When but it's not dynamic. If I add more Specialty instances I'll have to manually alter the code.
cases.annotate(
specialty=Case(
When(judge=0, then=Value('name 0 goes here')),
When(judge=1, then=Value('name 1 goes here')),
When(judge=2, then=Value('name 2 goes here')),
When(judge=3, then=Value('name 3 goes here')),
...
Can this be done dynamically? I look trough django's query reference docs but couldn't produce a working solution with the tools specified there.
You can do this with a subquery expression:
from django.db.models import OuterRef, Subquery
Case.objects.annotate(
specialty=Subquery(
Specialty.objects.filter(sys_num=OuterRef('judge')).values('name')[:1]
)
)
For some databases, casting might even be necessary:
from django.db.models import IntegerField, OuterRef, Subquery
from django.db.models.functions import Cast
Case.objects.annotate(
specialty=Subquery(
Specialty.objects.filter(sys_num=Cast(
OuterRef('judge'),
output_field=IntegerField()
)).values('name')[:1]
)
)
But the modeling is very bad. Usually it is better to work with a ForeignKey, this will guarantee that the judge can only point to a valid case (so referential integrity), will create indexes on the fields, and it will also make the Django ORM more effective since it allows more advanced querying with (relativily) small queries.

filter model using manytomanyfield

I've got these models :
work.py
class Work(models.Model):
...
network = models.ManyToManyField(Network,related_name='network')
network.py
class Network(models.Model):
...
users = models.ManyToManyField(User, related_name="users")
In my views.py I got this class-based generic ListView
class WorkList(PermsMixin, ListContextMixin, ListView):
model = Work
# Here I want to filter queryset
What I want to do is to filter the queryset such that the logged in user is in the network users.
I tried many things, for example
queryset = Work.objects.all()
queryset.filter('self.request.user__in=network_users')
But I got this error:
ValueError : too many values to unpack (expected 2)
Can anyone help me please?
This is not the way to write queries. Typically you pass to a .filter(..) named arguments, these follow a "language" that specify what you want.
In your case, you want - if I understand it correctly - all the Works for which there exists a related Network that belongs to that User, you do this with:
Work.objects.filter(network__users=self.request.user).distinct()
Here two consecutive underscores (__) are used to see "through" a relation. In case of a one-to-many relation, or a many-to-many relation, it is sufficient that one path (a path from Work through Network to user) exists.
The .distinct() should be used if you want to only return different Works: if you do not use this, the same Work object can be in the queryset multiple times, since there can be multiple Networks that belong to the given user and are part of that Work.
Extra remark: you define the related_names exactly the same as the field name, for example:
network = models.ManyToManyField(Network,related_name='network')
But the related_name is not the name of the relation you define there, the related name is the reverse relation: the name of the implicit relation you have defined between Network and Work. So that now means that you obtain a queryset of Works by writing some_network.network, which does not make sense. You typically thus give it a name like:
network = models.ManyToManyField(Network,related_name='work')

What is the best way to handle functional django model field defaults?

Sometimes a ForeignKey field needs a default. For example:
class ReleaseManager(BaseManager):
def default(self):
return self.filter(default=True).order_by('-modified').first()
class Release(BaseModel):
default = models.BooleanField(default=False)
...
class Server(models.Model):
...
release = models.ForeignKey(Release, null=True, default=Release.objects.default)
All is well and good with the above code until the time comes for db migration whereupon the functional default causes big problems because the default function cannot be serialized. Manual migration can work around this but on a large project where migrations are perhaps squashed periodically this leaves a time bomb for the unwary.
A common workaround is to move the default from the field to the save method of the model but this causes confusion if the model is used by things like the rest framework or in creating forms where the default is expected on the field.
My current favourite workaround works with migrations and with the rest framework and other form generation. It assumes the object manager supplies a default method and uses a specialized ForeignKey field to get at it:
class ForeignKeyWithObjectManagerDefault(models.ForeignKey):
def __init__(self, to, **kwargs):
super().__init__(to, **kwargs)
self.to = to
def get_default(self):
return self.to.objects.default().pk
class Project(SOSAdminObject):
primary = ForeignKeyWithObjectManagerDefault(Primary, related_name='projects')
...
Now migrations work as expected and we can use any functionality we like to supply a default object to a foreign key field.

Django form exclude options in select field

I have one model that has a ManyToMany Field (let's call it "Options") with another Model
When I create the ModelForm it displays all options.
Is there any way to exclude some option values or to show only some of them?
Here is an example:
models.py
class Options (model.Models):
name = ...
...
class Anything (model.Models):
...
options = ManyToManyField(Options)
values of "Options" in my DB:
["OK",
"OK_2",
"NOT_OK",
"OK_3,
"NOT_OK_2"]
Let's say that I need to show ONLY the "OK" values and hide or not to show the "NOT_OK" values.
Is there any way to do this with ModelForms?
You certainly can filter the queryset for a foreign key field or m2m on the related model by using a Form or more commonly a ModelForm.
The reason doing this at form level is useful is because that filtering could well be based on business logic which is not applicable in all cases and so allows more flexibility than defining it against the model for example.
While you can do this while defining the form fields it is best to do it once the form has been constructed and so it takes place at runtime and not compile time (I have just experienced a few interesting occasions where this has caused me some issues, however that was an earlier version of Django!)
The following ModelForm would do the job:
class AnythingForm(ModelForm):
options = forms.MultipleChoiceField()
def __init__(self, **kwargs):
super(AnythingForm, self).__init__(self, **kwargs)
self.fields['options'].queryset = Option.objects.filter({pass in your filters here...})
class Meta:
model = Anything
You can pass the limit_choices_to parameter to your ManyToMany field:
from django.db.models import Q
class Anything (models.Model):
options = models.ManyToManyField(Options,
limit_choices_to=Q(name__startswith='OK'))
In django 1.7 you can even pass a callable in case if list of choices should be changed dynamically.

How to set for m2m-field different querysets for each inline object?

So I have a model that is shown in inline form.
That model have ManyToManyField.
Imagine that there are several inline-objects that are already created.
The problem is how to show different querysets of available objects in my m2m-field based on original inline-object.
One more time:)
I mean that in each inline-object must by m2m-field with different available variants.
Variants will of course include all that is actually set for this inline-object + they must include only variants that are not present at the moment anywhere else.
Thanks.
Question is very poorly written, so it's hard to be sure exactly what you're looking for, but my best guess is that you're want to limit the queryset for the ManyToManyField to items that are not assigned to anything else? If that's correct:
(You also didn't post an example model, so I'll make one up to illustrate)
class SomeModel(models.Model):
my_m2m_field = models.ManyToManyField(OtherModel)
And, here's the code to limit the field based on that:
class SomeModelInlineAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyInlineAdminForm, self).__init__(*args, **kwargs)
self.fields['my_m2m_field'].queryset = OtherModel.objects.filter(somemodel__isnull=True)
class SomeModelInlineAdmin(admin.TabularInline):
model = SomeModel
form = SomeModelInlineAdminForm

Categories

Resources