I want empty job_users field before sending to the template. Because job_groups and job_users is dependent. I am calling ajax call when the group is select and users of that group will be displayed inside job_users. But now all users are displayed inside job_users select field.
class JobForm(forms.ModelForm):
job_description = forms.CharField(widget=forms.Textarea(attrs={'rows':4, 'cols':15}))
job_users = None
class Meta:
model = Jobs
fields = [
'job_name',
'job_group',
'job_users',
]
def __init__(self, *args, **kwargs):
self.user_company = kwargs.pop('user_company', None)
super().__init__(*args, **kwargs)
self.fields['job_group'].queryset = None
self.fields['job_group'].queryset = None i am using this but it is giving me error
Maybe you can do it like this:
class JobForm(forms.ModelForm):
job_description = forms.CharField(widget=forms.Textarea(attrs={'rows':4, 'cols':15}))
class Meta:
model = Jobs
fields = [
'job_name',
'job_group',
]
def __init__(self, *args, **kwargs):
self.user_company = kwargs.pop('user_company', None)
super().__init__(*args, **kwargs)
self.fields['job_group'].queryset = Jobgroup.objects.none()
But, it will throw error when you try to validate the form using form.is_valid(). So before doing that, update the queryset in the views like this:
def some_view_def(request):
form = JobForm(request.POST)
form.fields['job_group'].queryset = JobGroup.objects.filter(...) # <-- here
if form.is_valid():
# rest of the code
Related
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
Path('create-task/<int:project_id>', TaskCreateView.as_view(), name='create_task')
Update your view
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
template_name = "tasks/create.html"
# fields = ["name", "start_date", "due_date", "assignee"]
#NB: I have remove project from the field list, you may need to do the same in your form as it is handled elsewhere
form_class = TaskForm
def get_form(self, *args, **kwargs):
form = super(TaskCreateView, self).get_form(*args, **kwargs)
form.fields['assignee'].queryset = Project.members.filter(project_id = self.kwargs['project_id'])
Return form
def form_valid(self, form):
form.instance.project_id = project.self.kwargs['project_id']
return super(TaskCreateView, self).form_valid(form)
Add links
Create Task for this project
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
I have one model name is cityform
i want to get url parmeter in this CityFrom hwo can i do this?
here is my url
path('state/city/<int:id>/', City.as_view(), name="city")
http://localhost:8000/country/state/city/3/
here is my form
class
CityFrom(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CityFrom,self).__init__(*args, **kwargs)
print(args)
print(kwargs)
self.fields['state'] = forms.ModelChoiceField(
empty_label = 'Select',
queryset = State.objects.all()
)
class Meta:
model = City
fields = ('state', 'name')
in this form i want to access id = 3
here is my view
from django.views import View
class City(View):
def get(self, request, *args, **kwargs):
Forms = CityFrom()
return render(request, 'albums/add.html', {'Forms': Forms})
Pass url parameter as keyword argument from views.py as following.
form = CityFrom(id=kwargs.get("id"))
To get the id in your forms.py, use following code in your form's __init__ method.
self.id = kwargs.get('id')
Your form should look like this.
CityFrom(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.id = kwargs.get('id')
super(CityFrom,self).__init__(*args, **kwargs)
self.fields['state'] = forms.ModelChoiceField(
empty_label = 'Select',
queryset = State.objects.all()
)
class Meta:
model = City
fields = ('state', 'name')
* Call super after getting the id in your form as above. Here order of calling super is important.
Try
CityFrom(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.id = kwargs.pop('id')
super(CityFrom,self).__init__(*args, **kwargs)
class Report(models.Model):
# ....
product = models.ForeignKey(Product)
class Product(models.Model):
name = models.CharField(max_length=50)
class Item(models.Model):
box = models.ForeignKey(BoxInTransport)
product = models.ForeignKey(Product)
class BoxInTransport(models.Model):
transport = models.ForeignKey(Transport)
box = models.ForeignKey(Box)
This is - in short - the structure of models.
And I have a view which lets me create new report:
class ReportCreateView(CreateView):
model = Report
form_class = ReportForm
def get_form_kwargs(self):
# updating to get argument from url
kwargs = super(DifferenceCreateView, self).get_form_kwargs()
kwargs.update(self.kwargs)
return kwargs
and the form:
class ReportForm(ModelForm):
class Meta:
model = Report
fields = [
'product'
]
def __init__(self, box_nr=None, *args, **kwargs):
super(ReportForm, self).__init__(*args, **kwargs)
self.fields['product'].queryset = ???
How can I get only these products which belong to a specific box? To be more clear:
Only products which:
Item.objects.filter(box__box__box_code=box_nr)
Now I get all Items which I need, but I need to pass self.fields['products'] to only product form with this new Items queryset.
Can you help me?
EDIT
I've tried something like this:
def __init__(self, box_nr=None, *args, **kwargs):
super(ReportForm, self).__init__(*args, **kwargs)
queryset = Item.objects.filter(
box__box__box_code=boxno
)
none_queryset = Product.objects.none()
list_or_products = [p.product for p in queryset]
product_queryset = list(chain(none_queryset, list_or_products))
self.fields['product'].queryset = product_queryset
But, first - it looks little ugly :), second - it doesn't work:
AttributeError: 'list' object has no attribute 'all'
Your __init__ could look something like this:
def __init__(self, box_nr=None, *args, **kwargs):
super(ReportForm, self).__init__(*args, **kwargs)
qs = Product.objects.filter(item__box__box__box_code=box_nr)
self.fields['product'].queryset = qs
Basically, you need a reverse lookup on Product to Item. You can read the relevant documentation here
Note that: item__box__box__box_code=box_nr is based on my understanding of your models. item__box does the reverse lookup. Rest might need some tweaking based on your model definitions.
I'm trying to override concept queryset in my child form, to get a custom list concepts based on the area got from request.POST, here is my list of concepts, which i need to filter based on the POST request, this lists is a fk of my child form (InvoiceDetail). is it possible to have these filters?
after doing some test when I pass the initial data as the documentation says initial=['concept'=queryset_as_dict], it always returns all the concepts, but i print the same in the view and its ok the filter, but is not ok when i render in template, so I was reading that I need to use some BaseInlineFormset. so when I test I obtained different errors:
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
'InvoiceDetailFormFormSet' object has no attribute 'fields'
so here is my code:
models.py
class ConceptDetail(CreateUpdateMixin): # here, is custom list if area='default' only returns 10 rows.
name = models.CharField(max_length=150)
area = models.ForeignKey('procedure.Area')
class Invoice(ClusterableModel, CreateUpdateMixin): # parentForm
invoice = models.SlugField(max_length=15)
class InvoiceDetail(CreateUpdateMixin): # childForm
tax = models.FloatField()
concept = models.ForeignKey(ConceptDetail, null=True, blank=True) # fk to override using custom queryset
invoice = models.ForeignKey('Invoice', null=True, blank=True)
views.py
class CreateInvoiceProcedureView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
template_name = 'invoice/invoice_form.html'
model = Invoice
permission_required = 'invoice.can_check_invoice'
def post(self, request, *args, **kwargs):
self.object = None
form = InvoiceForm(request=request)
# initial initial=[{'tax': 16, }] removed
invoice_detail_form = InvoiceDetailFormSet(request.POST, instance=Invoice,
request=request)
return self.render_to_response(
self.get_context_data(
form=form,
invoice_detail_form=invoice_detail_form
)
)
forms.py
class BaseFormSetInvoice(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
# call first to retrieve kwargs values, when the class is instantiated
self.request = kwargs.pop("request")
super(BaseFormSetInvoice, self).__init__(*args, **kwargs)
self.queryset.concept = ConceptDetail.objects.filter(
Q(area__name=self.request.POST.get('area')) | Q(area__name='default')
)
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoice
fields = ('invoice',)
class InvoiceDetailForm(forms.ModelForm):
class Meta:
model = InvoiceDetail
fields = ('concept',)
InvoiceDetailFormSet = inlineformset_factory(Invoice, InvoiceDetail,
formset=BaseFormSetInvoice,
form=InvoiceDetailForm,
extra=1)
How can i fix it?, what do i need to read to solve this problem, I tried to debug the process, i didn't find answers.
i try to do this:
def FooForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['concept'].queryset = ConceptDetail.objects.filter(area__name='default')
In a inlineformset_factory how can do it?.
After a lot of tests, my solution is override the formset before to rendering, using get_context_data.
def get_context_data(self, **kwargs):
context = super(CreateInvoiceProcedureView, self).get_context_data(**kwargs)
for form in context['invoice_detail_form']:
form.fields['concept'].queryset = ConceptDetail.objects.filter(area__name=self.request.POST.get('area'))
return context
I would like not to display a field in form if I have a boolean field in database set to False.
Here is my code:
class CreateServer(ModelForm):
def __init__(self, g, *args, **kwargs):
super(CreateServer, self).__init__(*args, **kwargs)
if g.boolean_clients:
self.fields['clients'].queryset = Clients.objects.filter(game=g)
else:
# the fields['clients'] shouldn't be displayed in form
pass
...
class Meta:
model = Server
queryset = Server.objects.filter()
fields = ['hostname', 'clients', 'map']
So if g.boolean_clients is true, there must be the filter, but if g.boolean_clients is false I do not want to display this field in form.
Is there any way hot to do it?
I haven't tested this but try:
class CreateServer(ModelForm):
def __init__(self, g, *args, **kwargs):
super(CreateServer, self).__init__(*args, **kwargs)
if g.boolean_clients:
self.fields['clients'].queryset = Clients.objects.filter(game=g)
else:
self.fields.pop('clients')
class Meta:
model = Server
queryset = Server.objects.filter()
fields = ['hostname', 'clients', 'map']