Django Foreign Key Reverse Filtering - python

I am still relatively new to Django and still struggle somewhat with ForeignKey filtering and I'd appreciate any help with my problem. I have 2 models below and in my PositionUpdateForm I need the 'candidate' field choices to be only the applicants to that position.
class Position(models.Model):
title = models.CharField(max_length=128)
candidate = models.ForeignKey('careers.Applicant',
on_delete=models.SET_NULL,
related_name='candidates',
blank=True,
null=True
)
class Applicant(models.Model):
first_name = models.CharField(max_length=128)
blank=False,
)
position = models.ManyToManyField(Position,
related_name='applicants',
blank=True
)
In my form I was trying each of the following:
class PositionUpdateForm(forms.ModelForm):
candidate = forms.ModelChoiceField(queryset=Applicant.objects.filter(???))
def __init__(self, *args, **kwargs):
super(PositionUpdateForm, self).__init__(*args, **kwargs)
self.fields['candidate'].queryset = Applicant.objects.filter(???)
Thank you for any assistance.

If you want to have the Applicants that have a position to that Position, you can obtain that with:
class PositionUpdateForm(forms.ModelForm):
candidate = forms.ModelChoiceField(queryset=Applicant.objects.empty())
def __init__(self, *args, **kwargs):
super(PositionUpdateForm, self).__init__(*args, **kwargs)
self.fields['candidate'].queryset = Applicant.objects.filter(position=self.instance)
or we can use the relation in reverse:
class PositionUpdateForm(forms.ModelForm):
candidate = forms.ModelChoiceField(queryset=Applicant.objects.empty())
def __init__(self, *args, **kwargs):
super(PositionUpdateForm, self).__init__(*args, **kwargs)
self.fields['candidate'].queryset = self.instance.applicants.all()
Note that you can only use this when you update a Position model, since otherwise there are no related Applicant records of course.

Related

using initial on django MultipleChoiceField on many-to-many relation

In my model, I have:
flowcell = models.ForeignKey("FlowCell", on_delete=models.PROTECT)
lanes = models.ManyToManyField("main.FlowCellLane", related_name='demuxers', blank=True)
in my form, I want these to be selectable, based on available FlowCellLanes. So I pop flowcell to a variable and use it to see which 'Lanes' are there:
class DemuxerForm(forms.ModelForm):
class Meta:
model = Demuxer
exclude = ["owner", "pid", "flowcell"]
lanes = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple)
def __init__(self, *args, **kwargs):
self.flowcell = kwargs.pop('flowcell')
super().__init__(*args, **kwargs)
self.fields['lanes'].choices = sorted([(lane.pk, str(lane.lane_number))
for lane in self.flowcell.lanes.all()])
Now I would like to have all available checkboxes checked. But I don't know how I could do that. At the spot where initial= could be, 'self' is off course not available... any ideas?
I figured it out:
choices = self.flowcell.lanes.values_list("pk", lane_number").order_by("lane_number")
self.fields['lanes'].choices = choices
self.fields["lanes"].initial = [pk for pk, _ in choices]

Django QuerySet Any

I want to get a queryset of all books that are currently in Library (the dateReturn of a currently rent is set to null).
I'm new to python and i don't know how to do subqueries in django.
In other words in want to filter every related object field on a condition, if only one related-object doesn't match to this condition the object must not be returned
models.py
class Book(models.Model):
cod = models.CharField(max_length=255, unique=True)
title = models.CharField(max_length=255)
.....
class Rent(models.Model):
dateRent = models.DateField(default=timezone.now)
dateReturn = models.DateField(null=True, blank=True)
book = models.ForeignKey(modelsBook.Book, on_delete=models.DO_NOTHING, related_name="rent")
.....
P.S:
I need this subquery for display book currently not render in a choiceField
forms.py
class RentForm(forms.ModelForm):
__pk=None
def __init__(self, *args, **kwargs):
self.__pk = kwargs.pop('pk', None)
super(RentForm, self).__init__(*args, **kwargs)
class Meta():
model = models.Rent
fields = ('book', 'student')
labels = {
'book' : _('Libro'),
'student' : _('Studente'),
}
widgets = {
'book': queryset,
.....
You can filter objects through the related_name.
class RentForm(forms.ModelForm):
__pk=None
def __init__(self, *args, **kwargs):
self.__pk = kwargs.pop('pk', None)
super(RentForm, self).__init__(*args, **kwargs)
self.fields['book'].queryset = Book.objects.exclude(rent__dateReturn__isnull=True)
...

"<Story: title>" needs to have a value for field "id" before this many-to-many relationship can be used

I added code, so the user can connect story maximum with 2 genres, when I try to save story it brings error in title.
class Story(models.Model):
title = models.CharField(max_length=255)
genre = models.ManyToManyField(Genre)
alias = models.CharField(max_length=255, null=True, blank=True)
def save(self, *args, **kwargs):
self.alias = slugify(self.title, 'ru')
return super(Story, self).save(*args, **kwargs)
def clean(self, *args, **kwargs):
if self.genre.count() > 2:
raise ValidationError('Error')
super(Story, self).clean(*args, **kwargs)
Both objects need to be saved first in order to get id values, before the many-to-many relationship can be set. This is because the many-to-many relationship is stored using a third database table which holds all the (story.id, genre.id) pairs. You want something like:
my_genre = Genre(name='Fable')
my_genre.save()
my_story = Story(title='The Tortoise and the Hare')
my_story.save()
# now that my_genre.id and my_story.id are defined, the many-to-many relationship can be set:
my_story.genre.set([my_genre])
my_story.save()

Django manipulate model fields programmatically

I want to use the following base model to allow our team to add a unique_reference field to various models where required, using custom attributes (namely prefix, length and the field_name of the field).
is there a clean and agreeable way to achieve what I am looking for with Django?
class UniqueFieldModel(models.Model):
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
self.prefix = getattr(self.Meta, 'unique_reference_prefix')
self.unique_ref_length = getattr(self.Meta, 'unique_reference_field_name',
settings.UNIQUE_REFERENCE_MAX_LENGTH)
unique_ref_field_name = getattr(self.Meta, 'unique_reference_field_name')
unique_reference_field = models.CharField(
max_length=self.unique_ref_length,
blank=False,
null=False
)
setattr(self, unique_ref_field_name, unique_reference_field)
super(UniqueFieldModel, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
if not self.unique_reference:
prefix = getattr(self.Meta, 'unique_reference_prefix')
self.unique_reference = \
self.gen_unique_value(
prefix,
lambda x: get_random_string(x),
lambda x: self.objects.filter(unique_reference=x).count(),
self.unique_ref_length
).upper()
I would say that one possible way would be to normalise the name of the unique_reference field over all models.
But it is suboptimal, as it will require a lot of refactoring, and will be semantically incorrect in some cases.

Django multiple similar models

I want refactor some of my code in models because it's a little mess. I have couple models.
class Part(models.Model):
class Category(models.Model):
class Labor(models.Model):
And so on, seven in total. I am generating for them ID. For Part it is:
def save(self, *args, **kwargs):
if not Part.objects.count():
latest = 'XXX00000'
else:
latest = Part.objects.all().order_by('-par_id')[0].par_id
self.par_id = "PAR" + str(int(latest[3:]) + 1).zfill(5)
super(Part, self).save(*args, **kwargs)
And it's pretty similar for rest of classes. Only name of class is changing, three letters identification and paramtere in order_by. I was wondering how can I do it DRY. Because it's 7 lines of code on each class that should be somehow shortened.
I was wondering maybe create BaseModel class inherited from it and somehow change only mentioned things. I would like to get some directions how can I do it better.
Edit:
class Part(models.Model):
par_id = models.CharField(primary_key=True, unique=True, max_length=9, blank=False)
par_name = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if not Part.objects.count():
latest = 'XXX00000'
else:
latest = Part.objects.all().order_by('-par_id')[0].par_id
self.par_id = "PAR" + str(int(latest[3:]) + 1).zfill(5)
super(Part, self).save(*args, **kwargs)
class Category(models.Model):
cat_id = models.CharField(primary_key=True, unique=True, max_length=9)
cat_name = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if not Category.objects.count():
latest = 'XXX00000'
else:
latest = Category.objects.all().order_by('-cat_id')[0].cat_id
self.cat_id = "CAT" + str(int(latest[3:]) + 1).zfill(5)
super(Category, self).save(*args, **kwargs)
That are two o my classes.
Inheriting is definitely a good idea.
You're not giving much information about the models. So there are 2 main options for inheriting models:
A) To use an AbstractModel which would hold the common fields and some common methods. And then use child models to extend the fields and methods as you need. Here is an example from the django docs:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
B) If you're only interested in inheriting or extending the behavioural parts of your models (like the different methods for generating the id's), a proxy model would be a better option. Take a look at the docs: https://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models
Here is an example taken from the django docs:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):
# ...
pass
create class BaseModel(models.Model): and copypaste your save method there, but replace Part with self.__class__ , for example
class BaseModel(models.Model):
# some fields here
class Meta:
abstract = True
def save(self, *args, **kwargs):
first_declared_field = self.__class__._meta.fields[1].name
if self.__class__.objects.count():
latest = getattr(self.__class__.objects.order_by('-' + first_declared_field)[0], first_declared_field)
else:
latest = 'XXX00000'
field_value = first_declared_field.name.split('_')[0].upper() + str(int(latest[3:]) + 1).zfill(5)
setattr(self, first_declared_field, field_value)
super(BaseModel, self).save(*args, **kwargs)
class SomeChildModel(BaseModel):
pass

Categories