1

I'm developing a Property Management System, right now I'm working on an app named Property check, which basically saves info about inspections made on some properties.

A helpful user told me that I could achieve it using formsets. I'm trying to create a formset with my model TaskCheck. Every TaskCheck has a specific Task that belongs to one property. So this is what I've created:

views.py

def add_taskcheck(request, property_pk, pk):
    tasks = Task.objects.filter(property=property_pk)
    tasks_list = Task.objects.filter(property=property_pk).values('task')
    TaskCheckFormset = formset_factory(TaskCheckForm, extra=0)
    if request.method == 'POST':
        #do something
    else:
        formset = TaskCheckFormset(initial=task_list)

    context = {
        'title':"Add Property Check",
        'task':tasks,
        'reference':property_pk,
        'formset':formset,
    }
    return render(request, 'propertycheck/add-taskcheck.html', context)

My form looks like this:

TaskCheck form In this case, the Task "Sofas: Check" does not belong to the instance property, so it shouldn't be there, and the field Task should be pre-filled as initial data.

As far as I know from what I've read here I should pass initial data as a dict list. So I created "tasks_list" with .values() and tried to pass it as initial:

tasks_list = Task.objects.filter(property=property_pk).values('task')
formset = TaskCheckFormset(initial=task_list)

So my questions are:

How can I pre fill those fields with the queryset tasks?

How can I limit the number of rows to the number of queryset tasks objects?

Firstly I need to filter the Task objects that belong to a specific property.

I've tried using Model formsets but I couldn't pass the initial data. I've also read this question but I can't initiate it inside forms.

My models.py:

class Task(models.Model):
    task = models.CharField(max_length=100)
    category = models.ForeignKey(Categories)
    property = models.ManyToManyField(Property)

class TaskCheck(models.Model):
    status = models.CharField(choices=STATUS_CHOICES, default='nd', max_length=50)
    image = models.ImageField(upload_to='task_check', blank=True, null=True)
    notes = models.TextField(max_length=500, blank=True)
    task = models.ForeignKey(Task)
    property_check = models.ForeignKey(Propertycheck)
Luis Silva
  • 173
  • 13
  • I don't understand your formset, `formset_factory(TaskCheck, , form=TaskCheckForm, extra=0)` should throw an error since you pass it a model. You should be using a `modelformset_factory`. – dirkgroten Dec 30 '19 at 12:57
  • @dirkgroten sorry, with the changes between modelformset and formset I left that there. It's fixed now – Luis Silva Dec 30 '19 at 13:22

1 Answers1

1

If you have the pk of a Property, first fetch the actual objects:

property = get_object_or_404(Property, pk=property_pk)

Then create a queryset of all related TaskCheck objects:

qs = TaskCheck.objects.filter(task__property=property).distinct()

Finally you can initialise your model formset with qs:

TaskCheckFormset = modelformset_factory(TaskCheck, form=TaskCheckForm, fields=('status', 'notes'))
formset = TaskCheckFormset(request.POST, queryset=qs)
dirkgroten
  • 20,112
  • 2
  • 29
  • 42
  • I made those changes, I changed back again to modelformset. I forgot one detail on the queryset, this: `qs = TaskCheck.objects.filter(task__property=property, property_check=pcheck).distinct()`. Unfortunately the form didn't change much, it has just one empty row. How can I pass initial data now? – Luis Silva Dec 30 '19 at 13:47
  • Are you sure your queryset contains objects? The formset should have as many rows as objects in your queryset. – dirkgroten Dec 30 '19 at 13:52
  • It's empty but it's because this Propertycheck is empty. With your queryset it does render 3 rows but it also includes tasks that belong to other TaskCheck's – Luis Silva Dec 30 '19 at 14:01
  • I don't understand. You're showing a list of `TaskCheck` objects that you want to be able to edit. `task` is a field of `TaskCheck`, which is a FK to the `Task` model. So the selection is the list of all `Task` objects. What should be shown there? The current value should be pre-selected and your form allows to change it. If you don't want the user to change it, remove it from your form. – dirkgroten Dec 30 '19 at 14:04
  • You're right. The user shouldn't be able to change it, and I'll remove it. I just left it like that because these way I could see which row belongs to a specific Task. – Luis Silva Dec 30 '19 at 14:14
  • You can also disable the field in your form (so it's displayed but not editable). Or in your template use `{{ form.instance.task }}` to display the value without having it in the form. – dirkgroten Dec 30 '19 at 14:16
  • I've removed task field from the form and used `{{ form.instance.task }}` on my template. Right now the form is just like I wanted, also I tried with another properties and it shows a row for each related task. Now I'll take care of formset validation. After that I'll edited my post with the changes and accept you answer. Thank you so much!!! – Luis Silva Dec 30 '19 at 14:51