I have these models
class SubjectTeacher(models.Model):
teacher = models.ForeignKey(TeacherProfile, on_delete=models.CASCADE)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
SEMESTER_CHOICES = [("1st", "First Quarter"), ("2nd", 'Second Quarter'), ('3rd', 'Third Quarter'), ('4th', 'Fourth Quarter')]
semester = models.CharField(choices = SEMESTER_CHOICES, default = "1st", max_length=3)
students = models.ManyToManyField(StudentProfile, related_name="subjects")
def __str__(self):
return f'{self.subject.subject_name} | {self.teacher.user.first_name}'
class Meta:
constraints = [
UniqueConstraint(fields = ["teacher", "subject"], name = "Unique Subject Teacher")
]
class StudentGrade(models.Model):
subject_teacher = models.ForeignKey("SubjectTeacher", on_delete=models.CASCADE)
student = models.ForeignKey('StudentProfile', on_delete=models.CASCADE)
grade = models.IntegerField()
now I want StudentGrade.student to be based on what is selected (in django-admin) on the StudentGrade.subject_teacher
Example:
subject_teacher = <subject1> <subject2>
selected: <subject1>
student = choices from <subject1>.students
I already tried looking on similar cases such as editing the ModelForm but I couldn't get the gist of it.
If you want to use a model form that has a filtered queryset, you can do so like this:
# forms.py
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.students = StudentProfile.objects.filter( # your filter )
super(MyForm, self).__init__(*args, **kwargs)
self.fields['students'].queryset = self.students
If you are adding a StudentGrade directly in the Django Admin then it won't know which subject_teacher you want and will show a <select> with all the options. You would need to specify the subject_teacher before the form is initiated for the filter to work the way you want.
Related
Edit: Rewrote Description for clarity
I have a "CustomUser" class that I got from an all-auth tutorial (Django All-Auth Tutorial) and I have user as a foreign key in each model, which works as intended, only showing records pertaining to the current logged in user to that specific user.
For example:
EDUCATION MODEL (Works Correctly Here)
class Education(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
EducationInstitutionName = models.CharField(verbose_name=_('Institution Name'), max_length=100, default=None)
EducationLevel = models.CharField(verbose_name=_('Education Level'), choices=EDUCATIONLEVEL, max_length=100, default=None)
EducationStartDate = models.DateField(verbose_name=_('Education Start Date'), default=None)
EducationEndDate = models.DateField(verbose_name=_('Education End Date'), default=None)
EducationCaoCode = models.CharField(choices=CAO_CODE, max_length=100, default=None)
EducationDesc = models.CharField(verbose_name=_('Education Description'), max_length=250, default=None)
def __str__(self):
return self.EducationInstitutionName
This works perfectly and I am achieving what is needed.
The issue arises when I have a table comprised of Foreign Keys which is the focal point of my application which takes the constituent parts of a CV and allows you to combine them to make a CV of interchangable sections.
CV MODEL (Only model I have which is composed of Foreign Keys)
class Cv(models.Model):
user = models.ForeignKey(
CustomUser,
on_delete=models.CASCADE,
)
CvName = models.CharField(verbose_name=_('CvName'), max_length=100, default=None)
CvEducation = models.ForeignKey(Education, on_delete=models.CASCADE)
CvSkills = models.ForeignKey(Skills, on_delete=models.CASCADE)
CvWorkExperience = models.ForeignKey(WorkExperience, on_delete=models.CASCADE)
def __str__(self):
return self.CvName
CV FORM
This is where I'm hitting the issue - I want to filter the fields to only show records of Education, Skills and Work Experience for the current user - at the moment the dropdown shows every record in existence for the Education, Skills and Work Experience models rather than those ONLY pertaining to the current logged in user.
class CvForm(forms.ModelForm):
class Meta:
model = Cv
fields = ('CvName', 'CvEducation', 'CvSkills', 'CvWorkExperience')
# TRYING TO FILTER BY CURRENT USER
def __init__(self, user, *args, **kwargs):
super(CvForm, self).__init__(*args, **kwargs)
print(user)
self.fields['CvEducation'].queryset = Education.objects.filter(user=user)
self.fields['CvSkills'].queryset = Education.objects.filter(user=user)
self.fields['CvWorkExperience'].queryset = Education.objects.filter(user=user)
def save(self, commit=True):
cv = super(CvForm, self).save(commit=False)
cv.user = self.request.user
cv.CvName = self.cleaned_data['CvName']
cv.CvEducation = self.cleaned_data['CvEducation']
cv.CvSkills = self.cleaned_data['CvSkills']
cv.CvWorkExperience = self.cleaned_data['CvWorkExperience']
cv.save()
return cv
CV LIST VIEW (This - correctly - only shows completed CV records associated with the current logged in user)
class CvList(ListView):
model = Cv
fields = ['CvName', 'CvEducation', 'CvSkills', 'CvWorkExperience']
success_url = reverse_lazy('Cv_list')
def get_queryset(self):
user_ids = CustomUser.objects.filter(username=self.request.user).values_list('id', flat=True)
if user_ids:
for uid in user_ids:
return Cv.objects.filter(user__id=uid)
else:
return Cv.objects.all()
CV CREATE VIEW
class CvCreate(CreateView):
model = Cv
fields = ['CvName', 'CvEducation', 'CvSkills', 'CvWorkExperience']
success_url = reverse_lazy('Cv_list')
def form_valid(self, form):
form.instance.user = self.request.user
return super(CvCreate, self).form_valid(form)
def get_queryset(self):
user_ids = CustomUser.objects.filter(username=self.request.user).values_list('id', flat=True)
if user_ids:
for uid in user_ids:
return Cv.objects.filter(user__id=uid)
else:
return Cv.objects.all()
This gives me...
Dropdown showing the whole Education table rather than filtering by Current User
In this image as I'm logged in as John Doe I should not be able to see the record - "James Edu" as that is associated with a different user James.
This occurs in all three of the fields I have with Foreign Keys - CvEducation, CvSkills and CvWorkExperience
Fixed eventually (in a very roundabout way):
FORMS INIT METHOD
def __init__(self, *args, **kwargs):
initial_arguments = kwargs.get('initial', None)
initial_arguments_list = list(initial_arguments.values())
user_id = initial_arguments_list[0]
super(CvForm, self).__init__(*args, **kwargs)
self.fields['CvEducation'].queryset = Education.objects.filter(user__id=user_id)
self.fields['CvSkills'].queryset = Skills.objects.filter(user__id=user_id)
self.fields['CvWorkExperience'].queryset = WorkExperience.objects.filter(user__id=user_id)
CREATE VIEW
class CvCreate(CreateView):
model = Cv
success_url = reverse_lazy('Cv_list')
form_class = CvForm
exclude = ['user']
def get_initial(self):
self.initial.update({ 'created_by': self.request.user.id })
return self.initial
I am working on a set of product / category relationships in a Django application.
A product can belong to any category and needs to be ordered within that category, I am trying to do this using a Many to Many relationship with a "through=" option.
When a POST request is made via Ajax it takes the form of b'ordered_products=4&ordered_products=5', I received an error straight after the forms __init__ call that "5 is not one of the available choices" where 5 is the valid id of anOrderedCategoryManagedProduct object.
models.py
class Category(models.Model):
name = models.CharField(max_length=128)
slug = models.SlugField(max_length=128, unique=True)
class Product(models.Model):
name = models.CharField(max_length=128)
category_management = models.ManyToManyField(
Category,
related_name="category_managed_products",
through="OrderedCategoryManagedProduct",
blank=True,
verbose_name="Category Management",
)
class OrderedCategoryManagedProduct(SortableModel):
category = models.ForeignKey(
Category, on_delete=models.CASCADE, related_name="cm_products"
)
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name="cm_categories"
)
class Meta:
ordering = ["sort_order"]
def get_ordering_queryset(self):
return self.product.category_management()
class SortableModel(models.Model):
sort_order = models.IntegerField(db_index=True, null=True)
class Meta:
abstract = True
views.py
# POST = <QueryDict: {'ordered_products': [5, 4]}>
#staff_member_required
#permission_required("menu.manage_menus")
def ajax_reorder_menu_items(request, category_pk):
category = get_object_or_404(Category, pk=category_pk)
form = ReorderCategoryProductsForm(request.POST, instance=category)
status = 200
ctx = {}
if form.is_valid():
form.save()
elif form.errors:
status = 400
ctx = {"error": form.errors}
return JsonResponse(ctx, status=status)
forms.py
class ReorderCategoryProductsForm(forms.ModelForm):
ordered_products = OrderedModelMultipleChoiceField(
queryset=OrderedCategoryManagedProduct.objects.none()
)
class Meta:
model = Category
fields = ["id"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
self.fields["ordered_products"].queryset = self.instance.cm_products.all()
pass
def save(self):
for sort_order, category in enumerate(self.cleaned_data["ordered_products"]):
category.cm_products.sort_order = sort_order
category.save()
return self.instance
class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def clean(self, value):
qs = super().clean(value)
keys = list(map(int, value))
return sorted(qs, key=lambda v: keys.index(v.pk))
Big thanks to the #django IRC channel for the help on this, if anyone suffers the same problem the correct code was as follows:
class ReorderCategoryProductsForm(forms.ModelForm):
ordered_products = OrderedModelMultipleChoiceField(
queryset=OrderedCategoryManagedProduct.objects.all()
)
class Meta:
model = Category
fields = ["id"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
self.fields["ordered_products"].queryset = self.instance.cm_products.all()
pass
def save(self):
for sort_order, ocmp in enumerate(self.cleaned_data["ordered_products"]):
ocmp.sort_order = sort_order
ocmp.save()
return self.instance
Is It possible to add one or more Char Fields to each ManyToMany field option?
My Models:
class engineeringUni(models.Model):
field2 = models.CharField(max_length=200)
des_eng = models.CharField(max_length=1000, default='Add description')
def __str__(self):
return self.field2
def description_eng_universities(self):
return self.des_eng
class engineering_courses(models.Model):
course_name = models.CharField(max_length=400)
course_description = models.CharField(max_length=1000, default='This is a description')
course_offered_by = models.ManyToManyField(engineeringUni, related_name='course_offered_by')
course_duration = models.IntegerField(blank=False, default='2')
def __str__(self):
return self.course_name
def description_course(self):
return self.course_description
def offered_by_courses(self):
return self.course_offered_by
def duration_courses(self):
return str(self.course_duration)
As you can see in the image, I have the options in the ManyToMany field. Those options are:
University 1
University 2
University 3
What I want to have is an additional text (Char) field next to each of these options (University 1, University 2, University 3).
Is this possible?
EDIT 1:
Current code:
class engineering_courses(models.Model):
course_name = models.CharField(max_length=400)
course_description = models.CharField(max_length=1000, default='This is a description')
course_offered_by = models.ManyToManyField(
engineeringUni,
through='ThroughModel',
through_fields=('course', 'university'),
)
course_duration = models.IntegerField(blank=False, default='2')
def __str__(self):
return self.course_name
def description_course(self):
return self.course_description
def offered_by_courses(self):
return self.course_offered_by
def duration_courses(self):
return str(self.course_duration)
class ThroughModel(models.Model):
course = models.ForeignKey(engineering_courses, on_delete=models.CASCADE)
university = models.ForeignKey(engineeringUni, on_delete=models.CASCADE)
additional_text = models.CharField(max_length=200)
EDIT 2: Problem fixed. I was getting that no table error because I had deleted the migration files and on deleting database (db.sqlite3) file and applying migration again, It fixed.
You can use a through model in the ManyToManyField (docs). This model can be used to store any additional fields.
class engineering_courses(models.Model):
# ...
course_offered_by = models.ManyToManyField(engineeringUni, related_name='course_offered_by', through='ThroughModel')
class ThroughModel(models.Model):
course = models.ForeignKey(engineering_courses)
university = models.ForeignKey(engineeringUni)
additional_text = models.CharField()
Take another look at the django docs referenced in the answer from arjun27. You have more than one foreign key in your ThroughModel, so django is confused. Try specifying the through fields in your engineering_course model, migrate the changes, and see if that works.
Mark
I have a model that includes a foreign key:
class Part(models.Model):
partType = models.ForeignKey(PartType, on_delete=models.CASCADE)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
part_name = models.CharField(max_length=60)
class QuotePart(models.Model):
quote = models.ForeignKey(Quote, on_delete=models.CASCADE)
line = models.PositiveSmallIntegerField(default=1)
partType = models.ForeignKey(PartType, on_delete=models.CASCADE)
# part can be None if the part has not been selected
part = models.ForeignKey(Part, on_delete=models.CASCADE,blank=True,null=True)
I have a form that allows parts to be added to a quote and want to want to limit the choices on the form to just the Parts that are the right PartType but my code is not working:
class QuoteBikePartForm(ModelForm):
def __init__(self, *args, **kwargs):
super(QuoteBikePartForm, self).__init__(*args, **kwargs)
self.fields['partType'].widget.attrs['disabled'] = True
self.fields['frame_part'].widget.attrs['disabled'] = True
partType = kwargs.pop('partType')
self.fields['part'].queryset = Part.objects.filter(partType=partType.pk)
class Meta:
model = QuotePart
fields = ['quote','line','partType','frame_part', 'part', 'quantity','cost_price', 'sell_price']
QuoteBikePartFormSet = inlineformset_factory(Quote, QuotePart, form=QuoteBikePartForm)
I have tried a number of different things and so far no luck.
you can use 'self.instance.key_name' to the access the value.
Recently, I have been working in a generic inscription system to help students to register in a class or a lab. But I have problems with the logic of ManyToManyField in the Lab class.
from django.db import models
from django.contrib.auth.models import User
class Day(models.Model):
name = models.CharField(max_length=20, primary_key=True)
def __unicode__(self):
return u"%s" % self.name
class LabName(models.Model):
lab_name = models.CharField(max_length=50, primary_key=True)
class Meta:
verbose_name_plural = 'LabNames'
def __unicode__(self):
return u'%s' % self.lab_name
class Lab(models.Model):
name = models.ForeignKey(LabName)
start_hour = models.TimeField()
length = models.IntegerField(help_text="Given in minutes")
classDays = models.ManyToManyField(Day)
start_date = models.DateField()
finish_date = models.DateField()
max_cap = models.SmallIntegerField(help_text="Maximun number of students")
teacher = models.ForeignKey(User, related_name="Teachers")
students = models.ManyToManyField(User)
class Meta:
verbose_name_plural = 'Labs'
def __unicode__(self):
return u"%s %s" % (self.id, self.name)
I would prefer to associate an specific Group (django.contrib.auth.models.Group) called 'Students' rather than all the users or at least filter and/or validate this field to just add and view students and also do the same with the teacher field.
Update 1: I just noticed that maybe I could filter those users who are in a certain group using the optional parameter limit_choices_to.
The question is:
How can I use the limit_choices_to parameter to show only those users who are in the 'Students' group or the 'Teachers' group?
Ah, see that's MUCH clearer.
I would re-write {'id__in' : Group.objects.all().get(name='Teachers').user_set.all()}
to:
{'groups__name' : 'Teachers' }
Also, if you would like to keep your admin functionality separated from your models more (did you know Admin was originally completely in the models?), you can use formfield_for_foreignkey which is certainly a nice way to keep your models separated from admin junk.
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "teacher":
kwargs["queryset"] = User.objects.filter(groups__name='Teacher')
if db_field.name == "students":
kwargs["queryset"] = User.objects.filter(groups__name='Student')
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)