0

I am working on a Hostel management system and new in Django field. I have a model Student and Bed . The models code is below:

class Bed(models.Model):
    name = models.CharField("Bed No.",max_length=200)
    room = models.ForeignKey(Room, on_delete=models.CASCADE, default='1')

    class Meta:
        verbose_name_plural = "Bed"

    def __str__(self):
        return self.name

class Student(models.Model):
    name = models.CharField("name",max_length=200)
    cell_no = models.CharField("cell No",max_length=200)
    photo = models.ImageField(upload_to ='students_pics/')
    emergency_cell_no = models.CharField("Emergency Cell No", max_length=200)
    bed = models.ForeignKey(Bed, on_delete=models.CASCADE, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "Student"

    def __str__(self):
        return self.name

I want that when I select a bed from Student Model dropdown, there should only display the beds which are not already assigned to some other students.

I have tried something like:

bed = models.ForeignKey(Bed, on_delete=models.CASCADE, null=True, blank=True).exclude(----)

but it does not work. I have searched around Please help.

3 Answers3

0

you must filter this field from admin file.

solution 1 for filtering in admin) for example, define a status field for the bed model with true value as default, after that override save method into Student model and with creating a student with a select bed, set status bed field to false

2 for filtering) query on all students and check which beds don't assigned, then show into the admin

but for admin file you have to follow something like the below link enter link description here.

or link 2

or search django admin filtering key word

Hossein Asadi
  • 768
  • 1
  • 6
  • 15
0

Assuming you're using a form and do not want to do this in the admin, you can do the following. First, add a related_name= argument to your bed field on the Student model, like so (could be any string) and make migrations and migrate:

bed = models.ForeignKey(Bed, on_delete=models.CASCADE, null=True, blank=True, related_name='all_beds')

Then, using a model form with a custom __init__ method, you can populate your dropdown with only those beds that are not assigned.

class StudentForm(forms.ModelForm):
    class Meta:
        model = Student
        fields = ['name', 'cell_no', 'emergency_cell_no', 'bed', 'photo']

    def __init__(self, *args, **kwargs):
        super(StudentForm, self).__init__(*args, **kwargs)
        self.fields['bed'].queryset = Bed.objects.filter(all_beds__bed__isnull=True)

Here's what's happening here. The init method is populating the choices on the bed field with whatever .queryset you pass to it.

In this case, you're using the related_name "all_beds" to refer back to the Student model. The all_beds__bed is looking at Student.bed, and all_beds__bed__isnull=True is asking the database to return all Bed instances that do not have some existing relation to Student.bed. In other words, if a Bed instance has some populated bed field on the Student model, do not add it to the queryset.

When you call this model form in your view, for example:

form = StudentForm()

...it will populate accordingly. If you need that form to be dynamically created with data only available in your view, say an ID number or user data, you can instantiate the modelform class in your view. It doesn't HAVE to be in a seperate forms.py file.

Using Dynamic Forms

Now let's suppose you want to have an update view that excludes all the assigned beds, BUT includes the bed that was assigned to the student being updated.

Here's an example of how that could work, using a Q Object query in a dynamically instantiated form class within the view. You can do this by writing and calling a function that creates the ModelForm. The Q Object query combines unassigned beds with the one bed that IS assigned to the student instance.

Note that the create_form function takes in the request object. This isn't necessary as written, but I'm showing it because you may have a case where your logged in user wants to see the bed they have selected, in which case you could use Q(all_beds__user=request.user), assuming you have added a user foreign key to Student your model. Of course another way of doing this is to query the student based on request.user.

from django.db.models import Q

def updatestudent(request,id):
    student = Student.objects.get(pk=id)
    form = StudentForm(instance=student)

    def create_form(request, student):

        class StudentForm(forms.ModelForm):
            class Meta:
                model = Student
                fields = ['name', 'cell_no', 'emergency_cell_no', 'bed', 'photo']

            def __init__(self, *args, **kwargs):
                super (StudentForm, self).__init__(*args, **kwargs)
                self.fields['bed'].queryset = Bed.objects.filter(Q(all_beds__bed__isnull=True)|Q(all_beds__id=student.id))
        
        return StudentForm

    StudentForm = create_form(request, student)

    if request.method == 'POST':
        form = StudentForm(request.POST, instance=student)
        if form.is_valid():
            form.save()
            messages.success(request, "Student updated successfully.")
            return redirect('/student/view/'+id)
    else:
        form = StudentForm(instance=student)
        context= {'form':form}
        return render(request, 'new-student.html', context)

For more information about complex queries using Q objects, check out the documentation here.

Milo Persic
  • 985
  • 1
  • 7
  • 17
  • It worked according to my expectation. But when I update this model form the 'bed' field is not pre-populated with the bed already assigned to that Student. I have also instantiate the modelform. My code is: def updatestudent(request,id): student= Student.objects.get(pk=id) form = StudentForm(instance=student) if request.method == 'POST': form = StudentForm(request.POST, instance=student) if form.is_valid(): form.save() return redirect('/student/view/'+id) context= {'form':form} return render(request, 'std.html', context) – Abdul Jabbar Jun 17 '22 at 05:46
  • So, the form works for filtering when you create the Student record, but on update you do not see the association to Bed? You should add the code up in your post, not the comment section. Hard to tell what's going on (especially Python) which lives or dies on indenting! :) – Milo Persic Jun 17 '22 at 12:28
  • Thank you. I have added my this question code in the answer of this post. Please help there. – Abdul Jabbar Jun 17 '22 at 13:47
  • Okay - does this view save anything to the model? If you're using this to upload the ImageFile, you'll need `form = StudentForm(request.POST, request.FILES, instance=student)`, and `enctype="multipart/form-data"` in your `
    ` tag.
    – Milo Persic Jun 17 '22 at 15:24
  • Yes, the view saves the data perfectly also images. When I update the Student, All the fields are pre poulated except the bed field. It's happening after implementing your solution to my main question. Your solution works well when I add a new Student. But when update, the bed field is not pre polulated but other fields like Name, Cell No. are working perfectly etc. – Abdul Jabbar Jun 17 '22 at 15:48
  • That's odd. Are you sure the bed is being saved to the `Student` model? Can you see it in the Admin for example? – Milo Persic Jun 17 '22 at 17:22
  • Yes, The bed is being saved. It's available when I open the student is admin side. But when I try to update the model form, the bed field is empty, and the dropdown shows all the beds except the beds assigned to other students as well as this student. All the other fields e.g Name, Cell, Address have the value which were added at the time of Student creation. But the field is not populated with the bed which was assigned to the student. – Abdul Jabbar Jun 17 '22 at 19:14
  • Oh, this makes total sense now that I think about it. The `__init__` method is doing its job, meaning it won't show the user any bed that's been selected - even if it's the user's bed! There is a way around this. I hinted at it above when I mentioned dynamically calling the form class in the view. Essentially you'd tell the form to populate the field with all non-selected beds, EXCEPT when that bed was selected by the user. Does that make sense? This is actually an interesting problem! – Milo Persic Jun 17 '22 at 21:53
  • Let me know if you need help with that - I could add some example. I believe the solution is to use Q objects to build a query that excludes the selected beds, but adds in the user's bed. – Milo Persic Jun 17 '22 at 22:04
  • Yeah! Your are right. That's the problem. Please help now with some example are use the model/form code which is mentioned earlier in our discussion. Thanks a lot :) – Abdul Jabbar Jun 18 '22 at 00:07
  • I updated my answer with a way you can solve this, using a dynamic form in your view. I have not tested the code, but let me know if it works. – Milo Persic Jun 18 '22 at 02:30
0

The answer above works as expected in the question. But when I update the Student Form, the form field of "bed" is not pre populated with the values. I have also used instance=student. My code for update view is below:

def updatestudent(request,id):
    student = Student.objects.get(pk=id)
    form = StudentForm(instance=student)
    if request.method == 'POST':
        #print('Printing Post:', request)
        form = StudentForm(request.POST, instance=student)
        if form.is_valid():
            form.save()
            messages.success(request, "Student updated successfully.")
            return redirect('/student/view/'+id)

    context= {'form':form}
    return render(request, 'new-student.html', context)