Currently, when a user creates a task, they can assign it to all users. I only want them to be able to assign a task based on the members of the project. I feel like the concept I have right now works but I need to replace the ????. Task's assignee has a foreignkey relationship with the user_model. The user_model is also connected with members on a many to many relationship.
projects/models.py
class Project(models.Model):
name = models.CharField(max_length=200)
description = models.TextField()
members = models.ManyToManyField(USER_MODEL, related_name="projects")
tasks/models.py
class Task(models.Model):
name = models.CharField(max_length=200)
start_date = models.DateTimeField()
due_date = models.DateTimeField()
is_completed = models.BooleanField(default=False)
project = models.ForeignKey(
"projects.Project", related_name="tasks", on_delete=models.CASCADE
)
assignee = models.ForeignKey(
USER_MODEL, null=True, related_name="tasks", on_delete=models.SET_NULL
)
tasks/views.py
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
template_name = "tasks/create.html"
# fields = ["name", "start_date", "due_date", "project", "assignee"]
form_class = TaskForm
def get_form_kwargs(self):
kwargs = super(TaskCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
kwargs["project_members"] = ??????????
return kwargs
tasks/forms.py
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ["name", "start_date", "due_date", "project", "assignee"]
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
project_members = kwargs.pop("project_members")
super(TaskForm, self).__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(members=user)
self.fields["assignee"].queryset = Project.objects.filter(
members=?????????
)
Update: I followed SamSparx's suggestions and changed the URL paths so now TaskCreateView knows which project id. I updated my tasks/views to the following but I get a TypeError: "super(type, obj): obj must be an instance or subtype of type" and it points to the line: form = super(TaskForm, self).get_form(*args, **kwargs) Maybe it has something to do with having a get_form_kwargs and get_form function? I kept my existing features for the custom form such as when a user creates a task, they can only select projects they are associated with.
Views.py updated class TaskCreateView(LoginRequiredMixin, CreateView): model = Task template_name = "tasks/create.html"
form_class = TaskForm
def get_form_kwargs(self):
kwargs = super(TaskCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
def get_form(self, *args, **kwargs):
form = super(TaskForm, self).get_form(*args, **kwargs)
form.fields["assignee"].queryset = Project.members.filter(
project_id=self.kwargs["project_id"]
)
def form_valid(self, form):
form.instance.project_id = Project.self.kwargs["project_id"]
return super(TaskCreateView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy("list_projects")
I have also tried to update the forms.py with the following but get an error that .filter cannot be used on Many to Many relationships.
Updated forms.py
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ["name", "start_date", "due_date", "project", "assignee"]
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
super(TaskForm, self).__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(members=user)
self.fields["assignee"].queryset = Project.members.filter(
project_id=self.kwargs["project_id"]
)
Another thing I have tried is to go back to my first approach now that I have the url paths: tasks/create/(project_id)
Views.py
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
template_name = "tasks/create.html"
form_class = TaskForm
def get_form_kwargs(self):
kwargs = super(TaskCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
kwargs["project_id"] = Project.objects.all()[0].members.name
# prints to auth.User.none
return kwargs
I feel like if the kwargs["project_id"] line can be changed to getting list of members of whatever project with the ID in the URL, then this should solve it
Forms.py
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ["name", "start_date", "due_date", "project", "assignee"]
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
project_id = kwargs.pop("project_id")
super(TaskForm, self).__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(members=user)
self.fields["assignee"].queryset = Project.objects.filter(
members=project_id
)
The problem here is that your task doesn't know what members are relevant to include as assignees until you have chosen the project the task belongs to, and both project and assignee are chosen in the same form, so Django doeesn't know who is relevant yet.
The easiest way to handle this is to ensure the call to create a task is associated with the project it is going to be for - eg,
Update your URLs to handle the project ID
Update your view
Return form
Add links
This will create a link on the project details page, or underneath the project in a listview to 'create task for this project', carrying the project informaton for the view via the URL. Otherwise you will have to get into some rather more complex ajax calls that populate the potential assignees list based on the selection within the project dropdown in a dynamic fashion