I'm new to django and testing, so I'm not sure if there is a more simple solution to this question.
I'm creating an assessment app with a rubric that the user can edit, submit and update. Each rubric has a preset number of row models that are connected to the rubric model via a foreign key relationship. The user should be able to update multiple row_choice fields in multiple row models and post the row_choice fields to the database.
To portray this in a template, I decided to use a ModelFormSet and iterate over the ModelFormSet in rubric.html. This works OK, but whenever I try to test this layout using TestCase, I receive the error
['ManagementForm data is missing or has been tampered with']. I understand this error because the test does not pass a post request to the view using rubric.html (where ManagementForm is located), but the application works in the browser because the django template renders ManagementForm as html which has no problem in the view.
Can you test a ModelFormSet in django using TestCase, or do you have to use LiveServerTestCase and Selenium? Is there a way to get the example test to pass and still test a post request (while using ModelFormSet)? Any help is greatly appreciated.
forms.py
class RowForm(ModelForm):
class Meta:
model = Row
fields = ['row_choice']
class RubricForm(ModelForm):
class Meta:
model = Rubric
fields = ['name']
RowFormSet = modelformset_factory(Row, fields=('row_choice',), extra=0)
an example of a failing test:
def test_rubric_page_can_take_post_request(self):
self.add_two_classes_to_semester_add_two_students_to_class_add_one_row()
request = HttpRequest()
request.method = "POST"
response = rubric_page(request, "EG5000", "12345678")
self.assertEqual(response.status_code, 302)
and the Traceback:
Traceback (most recent call last):
File "C:\python33\assessmenttoolstaging\source\rubricapp\tests\tests.py", line 240, in test_rubric_page_can_take_post_request
response = rubric_page(request, "EG5000", "12345678")
File "C:\python33\assessmenttoolstaging\source\rubricapp\views.py", line 52, in rubric_page
RowFormSetWeb.clean()
File "C:\Python33\lib\site-packages\django\forms\models.py", line 645, in clean
self.validate_unique()
File "C:\Python33\lib\site-packages\django\forms\models.py", line 651, in validate_unique
forms_to_delete = self.deleted_forms
File "C:\Python33\lib\site-packages\django\forms\formsets.py", line 205, in deleted_forms
if not self.is_valid() or not self.can_delete:
File "C:\Python33\lib\site-packages\django\forms\formsets.py", line 304, in is_valid
self.errors
File "C:\Python33\lib\site-packages\django\forms\formsets.py", line 278, in errors
self.full_clean()
File "C:\Python33\lib\site-packages\django\forms\formsets.py", line 325, in full_clean
for i in range(0, self.total_form_count()):
File "C:\Python33\lib\site-packages\django\forms\formsets.py", line 115, in total_form_count
return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
File "C:\Python33\lib\site-packages\django\forms\formsets.py", line 97, in management_form
code='missing_management_form',
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
rubric_page view
def rubric_page(request, edclass, studentname):
edClassSpaceAdded = re.sub('([A-Z]+)', r'\1 ', edclass)
enrollmentObj = Enrollment.objects.get(edclass__name=edClassSpaceAdded, student__lnumber=studentname)
rubricForClass = enrollmentObj.keyrubric.get()
rows = Row.objects.filter(rubric=rubricForClass)
student = Student.objects.get(lnumber=studentname)
if request.method == 'POST':
#TestCase cannot test this section of the view
RowFormSetWeb = RowFormSet(request.POST)
RowFormSetWeb.clean()
if RowFormSetWeb.is_valid():
savedFormset = RowFormSetWeb.save(commit=False)
for i in savedFormset:
i.rubric = rubricForClass
RowFormSetWeb.save()
return redirect('/'+ edclass + '/')
else:
return render(request, 'rubric.html', {'studentlnumber': student.lnumber,'studentname': student.lastname + ", " + student.firstname, 'RowFormSetWeb':RowFormSetWeb, 'rows':rows, 'edclass':edclass})
else:
RowFormSetWeb = RowFormSet(queryset=Row.objects.filter(rubric=rubricForClass))
return render(request, 'rubric.html', {'studentlnumber': student.lnumber,'studentname': student.lastname + ", " + student.firstname, 'RowFormSetWeb':RowFormSetWeb, 'rows':rows, 'edclass':edclass})
form section of rubric.html
<h3 id="rubricheader">TODO Pull model into view</h3>
<form method="post" action= {% url 'rubricpage' edclass=edclass studentname=studentlnumber %}>
<table border="1">
<!-- TODO fix this so that it pulls from forms.py -->
<tr>
<th></th>
<th>Excellent</th>
<th>Proficient</th>
<th>Sub-par</th>
<th>Abysmal</th>
</tr>
{{ RowFormSetWeb.management_form }}
{% for form in RowFormSetWeb %}
{{ form.id }}
<tr>
<td>{{ form.row_choice }}</td><td>{{ form.excellenttext }}</td><td>{{ form.proficienttext }}</td><td>{{ form.satisfactorytext }}<td>{{ form.unsatisfactorytext }}</td>
</tr>
{{ RowFormSetWeb.errors }}
{% endfor %}
</table>
<input name="submitbutton" type="submit" name="submit" value="Submit" id="rubricsubmit">
{% csrf_token %}
</form>
{% endblock %}
models.py
from django.db import models
class Student(models.Model):
firstname = models.TextField(default="")
lastname = models.TextField(default="")
lnumber = models.TextField(default="")
def __str__(self):
return self.lnumber
#TODO add models
class EdClasses(models.Model):
name = models.TextField(default='')
students = models.ManyToManyField(Student, through="Enrollment")
def __str__(self):
return self.name
class Semester(models.Model):
text = models.TextField(default='201530')
classes = models.ManyToManyField(EdClasses)
def __str__(self):
return self.text
class Rubric(models.Model):
name = models.TextField(default="Basic Rubric")
def __str__(self):
return self.name
class Row(models.Model):
CHOICES = (
(None, 'Your string for display'),
('1','Excellent'),
('2','Proficient'),
('3','Awful'),
('4','The worst ever'),
)
rubric = models.ForeignKey(Rubric)
row_choice = models.CharField(max_length=20,choices=CHOICES, default="None", blank=True)
excellenttext = models.TextField(default="")
proficienttext = models.TextField(default="")
satisfactorytext = models.TextField(default="")
unsatisfactorytext = models.TextField(default="")
def __str__(self):
return self.row_choice
class Enrollment(models.Model):
student = models.ForeignKey(Student)
edclass = models.ForeignKey(EdClasses)
grade = models.TextField(default='')
keyrubric = models.ManyToManyField(Rubric)
How the form is rendered in the browser:
<form action="/201530/EG5000/21743148/" method="post">
<table border="1">
<!-- TODO fix this so that it pulls from forms.py -->
<tr>
<th></th>
<th>Excellent</th>
<th>Proficient</th>
<th>Sub-par</th>
<th>Abysmal</th>
</tr>
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="2"/><input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="2"/><input id="id_form-MIN_NUM_FORMS" name="form-MIN_NUM_FORMS" type="hidden" value="0"/><input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000"/>
<input id="id_form-0-id" name="form-0-id" type="hidden" value="3"/>
<tr>
<td><select id="id_form-0-row_choice" name="form-0-row_choice">
<option value="">Your string for display</option>
<option value="1">Excellent</option>
<option value="2">Proficient</option>
<option value="3">Awful</option>
<option value="4">The worst ever</option>
</select></td>
<td>THE BEST!</td>
<td>THE SECOND BEST!</td>
<td>THE THIRD BEST!</td>
<td>YOURE LAST</td>
</tr>
[]
<input id="id_form-1-id" name="form-1-id" type="hidden" value="4"/>
<tr>
<td><select id="id_form-1-row_choice" name="form-1-row_choice">
<option value="">Your string for display</option>
<option value="1">Excellent</option>
<option value="2">Proficient</option>
<option value="3">Awful</option>
<option value="4">The worst ever</option>
</select></td>
<td>THE GREATEST!</td>
<td>THE SECOND BEST!</td>
<td>THE THIRD BEST!</td>
<td>YOURE LAST</td>
</tr>
[]
</table>
<input id="rubricsubmit" name="submit" type="submit" value="Submit">
<input name="csrfmiddlewaretoken" type="hidden" value="0OeU2n0v8ooXHBxdUfi26xxqMIdrA50L"/>
</input></form>
One approach I've used in the past, albeit not a particularly nice one, is to use the client to get the rendered form, then use something like BeautifulSoup to parse out all the form data from that and update where necessary before posting back. That way you will get all the hidden and prepopulated fields, so you can be sure your tests behave the same way as a user would.
Related
My form:
class TestCaseUpdateForm(forms.ModelForm):
class Meta:
model = TestCase
fields = ( 'name', 'executable', 'parameter_list', 'executable_type', 'test_type' , 'project_name', 'created_by')
My views:
class EditCaseView(UpdateView):
model = TestCase
form_class = TestCaseUpdateForm
template_name_suffix = '_update_form'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get(self.pk_url_kwarg)
queryset = self.get_queryset().filter(pk=pk)
case = queryset.get()
context['my_case'] = case
context['my_exetype'] = TestCase.EXECUTABLE_TYPE_CHOICES
context['my_ttype'] = TestCase.TEST_TYPE_CHOICES
context['projnames'] = PROJNAME_CHOICES
return context
def get_success_url(self):
print("get_success_url")
return reverse("integratedTest:testCase")
My model:
PROJNAME_CHOICES = (
('Name1', 'Name1'),
('Name2','Name2'),
('Name3','Name3')
)
class TestCase(models.Model):
EXECUTABLE_TYPE_CHOICES = (
('e_type1', 'e_type1'),
('e_type2', 'e_type2'),
('e_type3', 'e_type3'),
)
TEST_TYPE_CHOICES = (
('t_type1', 't_type1'),
('t_type2','t_type2'),
('others', 'others'),
)
name = models.CharField(max_length=200, unique=True)
executable = models.CharField(max_length=1023)
parameter_list = models.TextField(blank=True, default = "")
project_name = models.CharField(max_length=200, choices = PROJNAME_CHOICES, default="Name1")
executable_type = models.CharField(max_length=200, choices = EXECUTABLE_TYPE_CHOICES, default = "e_type1")
test_type = models.CharField(max_length=200, choices = TEST_TYPE_CHOICES, default = "t_type1")
created_by = models.CharField(max_length=200, default = "sb")
create_datetime = models.DateTimeField("testcase created on", auto_now = True)
My template:
<form method="post">{% csrf_token %}
<h1 >Edit Test Case:</h1>
<table>
<tr>
<th>Name:</th>
<th>executable:</th>
<th>parameter_list:</th>
</tr>
<tr>
<td><input type="text" id="id_name" name="name" value = "{{my_case.name}}"
placeholder="short_unique_string" required>
</td>
<td>
<input type="text" id="id_executable" name="executable" value = "{{my_case.executable}}"
placeholder="FilenameWithoutPath.py" required>
</td>
<td><textarea id="id_parameter_list" name="parameter_list" value = "{{my_case.parameter_list}}"
placeholder="Copy your parameter string here directly"></textarea>
</td>
</tr>
<tr>
<th>executable_type:</th>
<th>test_type:</th>
<th>project_name:</th>
<th>created_by:</th>
</tr>
<tr>
<td>
<select name="executable_type" id="id_executable_type" required>
{%for key, value in my_exetype %}
{%if my_case.executable_type == key %}
<option value={{value}} selected>{{key}}</option>
{%else%}
<option value={{value}}>{{key}}</option>
{%endif%}
{%endfor%}
</select>
</td>
<td>
<select name="test_type" id="id_test_type" required>
{%for key, value in my_ttype %}
{%if my_case.test_type == key %}
<option value={{value}} selected>{{key}}</option>
{%else%}
<option value={{value}}>{{key}}</option>
{%endif%}
{%endfor%}
</select>
</td>
<td>
<select name="project_name" id="id_project_name" >
{%for key, value in projnames %}
{%if my_case.project_name == key %}
<option value={{value}} selected>{{key}}</option>
{%else%}
<option value={{value}}>{{key}}</option>
{%endif%}
{%endfor%}
</select>
</td>
<td><input type="text" id="id_created_by" name="created_by" value = "{{my_case.created_by}}"
placeholder="some name like 'sb'" required></td>
</tr>
</table>
{% for i in form.errors %}
{{i}}
{% endfor %}
<br>
{% if form.errors %}
error!
{% endif %}
<input type="submit" value="Update">
</form>
MY issue: The page displays well. I changed something and clicked update. The page refreshed to unchanged page, without calling get_success_url() as I expected. I checked the database, record didn't update either. I checked the template again and again but didn't find error.
Following Selcuk advice, I add print form.errors. When the editing page is display, it prints all field names as error like below:
When I update, print as:
I'm not sure what's that means.
I've made a Model Form for a model I have, I cannot directly save data to model because I have a custom format date field which can only be made in forms.py with forms.DateField(input_formats=settings.DATE_INPUT_FORMATS) but for some reason the data I'm parsing through the views is not saving in the model.
views.py
def applications_view(request):
if request.user.is_superuser:
applications = LeaveApplication.objects.all()
if request.method == "POST":
id = request.POST.get('applications_id')
user = request.POST.get('applications_user')
reason = request.POST.get('applications_reason')
from_date = request.POST.get('applications_from_date')
to_date = request.POST.get('applications_to_date')
if request.POST.get('approve'):
something = LeaveList(user=user, reason=reason, from_date=from_date,
to_date=to_date, leave_status='Approve')
leave_list_form = LeaveListForm(request.POST, instance=something)
if leave_list_form.is_valid():
leave_list_form.save()
elif request.POST.get('deny'):
something = LeaveList(user=user, reason=reason, from_date=from_date,
to_date=to_date, leave_status='Denied')
leave_list_form = LeaveListForm(request.POST, instance=something)
if leave_list_form.is_valid():
leave_list_form.save()
context = {
'applications': applications
}
return render(request, 'home/applications.html', context)
forms.py
class LeaveListForm(forms.ModelForm):
from_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS)
to_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS)
class Meta:
model = LeaveList
fields = [
'user',
'reason',
'from_date',
'to_date',
'leave_status'
]
I'm not rendering form with context on the template because I'm looping over data from another model which I'm fetching again with a button with the help of a hidden type input which is rendered with every loop.
<table style="width:100%">
<tr>
<th>Request ID</th>
<th>Employee</th>
<th>Reason for Leave</th>
<th>From Date</th>
<th>To Date</th>
<th>Approve/Deny</th>
</tr>
<tr>
<td>{{ applications.id }}</td>
<td>{{ applications.user }}</td>
<td>{{ applications.reason }}</td>
<td>{{ applications.from_date }}</td>
<td>{{ applications.to_date }}</td>
<td>
<form method="POST">
{% csrf_token %}
<input type="hidden" id="applications_id" name="applications_id" value="{{ applications.id }}">
<input type="hidden" id="applications_user" name="applications_user" value="{{ applications.user }}">
<input type="hidden" id="applications_reason" name="applications_reason" value="{{ applications.reason }}">
<input type="hidden" id="applications_from_date" name="applications_from_date" value="{{ applications.from_date }}">
<input type="hidden" id="applications_to_date" name="applications_to_date" value="{{ applications.to_date }}">
<input class="appproveButton appproveButtonYes" type="submit" name="approve" value="Approve">
<input type="submit" class="appproveButton appproveButtonNo"name="deny" value="Deny">
</form>
</td>
</tr>
</table>
Since you are already using your form why even handle getting the data? Simply pass the POST data to the form and let it handle it. Also it seems you don't want to get the leave_status from the form and instead pass a different key in the form for that, If that is the case just remove them from your form and handle it yourself:
from django.shortcuts import redirect
def applications_view(request):
if request.user.is_superuser:
applications = LeaveApplication.objects.all()
if request.method == "POST":
form = LeaveListForm(request.POST)
if form.is_valid():
if request.POST.get('approve'):
leave_status = 'Approve'
elif request.POST.get('deny'):
leave_status = 'Denied'
else:
leave_status = 'SOME_DEFAULT_VALUE'
form.instance.leave_status = leave_status
form.save()
return redirect('<SOME_URL_NAME>') # Redirect after successful form save!
context = {
'applications': applications,
}
return render(request, 'home/applications.html', context)
Now since you use a different view to render the form:
def other_view(request, *args, **kwargs):
form = LeaveListForm()
# rest of view
context = {..., 'form': LeaveListForm()}
return render(request, 'some_template.html', context)
In your form:
class LeaveListForm(forms.ModelForm):
from_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS) # Make sure the input formats are correct
to_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS)
class Meta:
model = LeaveList
fields = [
'user',
'reason',
'from_date',
'to_date',
] # Removed leave_status from here
Also it appears that you are directly rendering the form yourself by writing html. Even if you want to do that you need to work with the form in mind and set the names of the inputs to what the form would expect! Either simply leave the form rendering to django or correct your input tags names. I would suggest leaving the form rendering to django:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="approve" value="Approve">
<input type="submit" name="deny" value="Deny">
</form>
If you don't like the way Django renders forms and want to customize it there are lots of great packages out there to help you do that, for e.g. django-widget-tweaks and django-crispy-forms.
I'm trying to create an inventory management system. I'm having problems figuring out how to add multiple inventory items into my order from a table. I want to achieve this by selecting the item by the checkbox and also adding the quantity. I also eventually want to add a search bar to search through the table
html
<form method="POST" action="">
{% csrf_token %}
{% for field in form %}
<div class="form-group row">
<Label for="id_{{ field.name }}" class="col-2 col-form-label">{{ field.label }}</Label>
<div class="col-10">
{{ field }}
</div>
</div>
{% endfor %}
<table class="table table-striped">
<tbody>
{% for item in Inventory %}
<tr>
<td> <input type="checkbox" name="itemCheck" value="{{ item.pk }} "></td>
<td> <input name="itemQuantity"> </td>
<td> {{ item.name }} </td>
<td> {{ item.quantity }} </td>
<td> <span class="badge badge-pill badge-success">{{item.status}}</span></td>
<td> ${{ item.sale_price }} </td>
</td>
</tr>
{% endfor %}
</tbody>
</table>
views.py
def create_order(request):
order_form = OrderForm(request.POST)
if request.method == 'POST':
if formset.is_valid():
total = 0
order = Orders(total=total)
order.save()
order_form.save()
selected_items = request.POST.getlist('itemCheck')
print(selected_items) # This returns the primary keys of the selected items
context = {"form": order_form, "Inventory": Inventory.objects.all()}
return render(request, 'create_order.html', context)
models
class Inventory(models.Model):
name = models.CharField(max_length=128, blank=False)
...
def __str__(self):
return f"{self.id} - {self.name}"
class Orders(models.Model):
studio = models.CharField(max_length=64)
status = models.CharField(max_length=64, default="warehouse", blank=False)
total = models.DecimalField(max_digits=10, decimal_places=2)
class OrderEquipment(models.Model):
equipment = models.ForeignKey(Inventory, blank=False, on_delete=models.CASCADE)
order = models.ForeignKey(Orders, on_delete=models.CASCADE)
quantity = models.IntegerField(blank=False)
For the sake of simplicity, since you didn't post your formset and other code, I'm going to assume your HTML and formset are coming in correctly and just focus on the table/model loading. I have found the best way to load many items is to use bulk_create(). You may need to fidget around with the below code, but it should get you on the right track.
from django.db import transaction
try:
with transaction.atomic():
# I put cleaned_data below. You need to replace with your Orders data if different.
order = Orders.objects.create(**cleaned_data)
# Loop thru OrderEquipment Items. I'm guessing your 'selected_items' is the inventory data, (equipment and quantity).
orderequip_list = []
for equip in selected_items:
orderequip_list.append(OrderEquipment(order=order, **equip))
OrderEquipment.objects.bulk_create(orderequip_list)
except (IntegrityError, ObjectDoesNotExist):
order = None
I am trying to make a form for user creation through django. The user(henceforth developer) can choose from a list of supervisors to get himself registered. Problem is, I am not getting the list of all the supervisors from the query. When I use objects.get(), I receive an error that 2 objects were received. That means that the queries are getting the rows from the database.
models.py
from django.db import models
class UserProfile(models.Model):
name = models.CharField(max_length=50,verbose_name="Name")
login = models.CharField(max_length=(25),verbose_name="Login")
password = models.CharField(max_length=100, verbose_name="Password")
phone = models.CharField(max_length=20, verbose_name="Phone number", null=True, default=None, blank=True)
born_date = models.DateField(verbose_name="Born date" , null=True,default=None, blank=True)
last_connection = models.DateTimeField(verbose_name="Date of last connection" , null=True, default=None, blank=True)
email = models.EmailField(verbose_name="Email")
years_seniority = models.IntegerField(verbose_name="Seniority", default=0)
date_created = models.DateField(verbose_name="Date of Birthday", auto_now_add=True)
def __str__(self):
return self.name
class Supervisor(UserProfile):
specialisation = models.CharField(max_length=50, verbose_name="Specialisation")
class Developer(UserProfile):
supervisor = models.ForeignKey(Supervisor, verbose_name="Supervisor")
The form view create_developer.py -
from django.shortcuts import render
from django.http import HttpResponse
from TasksManager.models import Supervisor, Developer
# View for create_developer
def page(request):
error = False
# If form has posted
if request.POST:
if 'name' in request.POST:
name = request.POST.get('name', '')
else:
error=True
if 'login' in request.POST:
login = request.POST.get('login', '')
else:
error=True
if 'password' in request.POST:
password = request.POST.get('password', '')
else:
error=True
if 'supervisor' in request.POST:
supervisor_id = request.POST.get('supervisor', '')
else:
error=True
if not error:
supervisor = Supervisor.objects.get(id = supervisor_id)
new_dev = Developer(name=name, login=login, password=password,supervisor=supervisor)
new_dev.save()
return HttpResponse("Developer added")
else:
return HttpResponse("An error as occured")
else:
supervisors_list = Supervisor.objects.all()
return render(request, 'en/public/create_developer.html')
template create_developer.html
{% extends "base.html" %}
{% block title_html %}
Create Developer
{% endblock %}
{% block h1 %}
Create Developer
{% endblock %}
{% block article_content %}
<form method="post" action="{% url 'create_developer' %}" >
<table>
<tr>
<td>Name</td>
<td>
<input type="text" name="name" />
</td>
</tr>
<tr>
<td>Login</td>
<td>
<input type="text" name="login" />
</td>
</tr>
<tr>
<td>Password</td>
<td>
<input type="text" name="password" />
</td>
</tr>
<tr>
<td>Supervisor</td>
<td>
<select name="supervisor">
{% for supervisor in supervisors_list %}
<option value="{{ supervisor.id }}">{{ supervisor.name}}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="Valid" />
</td>
</tr>
</table>
</form>
{% endblock %}
The supervisor select list should show the list. But I am getting an empty list there. The output of {{ supervisors_list|length }} is zero.
How to debug this?
You have to pass context to the html:
from django.shortcuts import render
from django.http import HttpResponse
from TasksManager.models import Supervisor, Developer
# View for create_developer
def page(request):
error = False
# If form has posted
if request.POST:
if 'name' in request.POST:
name = request.POST.get('name', '')
else:
error=True
if 'login' in request.POST:
login = request.POST.get('login', '')
else:
error=True
if 'password' in request.POST:
password = request.POST.get('password', '')
else:
error=True
if 'supervisor' in request.POST:
supervisor_id = request.POST.get('supervisor', '')
else:
error=True
if not error:
supervisor = Supervisor.objects.get(id = supervisor_id)
new_dev = Developer(name=name, login=login, password=password,supervisor=supervisor)
new_dev.save()
return HttpResponse("Developer added")
else:
return HttpResponse("An error as occured")
else:
supervisors_list = Supervisor.objects.all()
return render(request, 'en/public/create_developer.html', {'supervisors_list' : supervisors_list})
Lets say I have a model
models.py
class user:
name = models.CharField(max_length=25)
class job:
job_name = models.CharField(max_length=25)
class user_job:
user = models.ForeignKey('user')
job = models.ForeignKey('job')
forms.py
jobs = (
('0', 'a'),
('1', 'b'),
('2', 'c'),
)
class searchForm:
box = forms.ModelMultipleChoiceField(
choices = jobs,
widget = forms.CheckboxSelectMultiple(),
label = 'Search',
)
I can search for users that have job 'a' with
user_job.objects.filter(job__exact = 'a') ...
I tried to search for users that have both job 'a' and job 'c' like so
search_q = user_job.objects.filter(job__exact = 'a')
search_q = search_q.filter(job__exact = 'c')
but I get all users that have job 'a' or job 'c', and I need all users that have both jobs.
Is there a way I can filter it through Django, or do I need to filter on one job and then iterate through the results and check for the 2nd job?
You'll probably find it easier to search from the User model since what you want is a list of Userss who have both jobs. Django automatically sets up properties on your models that allow you to access related models both from model instances and in DB queries.
Assuming you set up your models like so:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=25)
def __repr__(self):
return '<User: %s>' % self.name
class Job(models.Model):
name = models.CharField(max_length=25)
def __repr__(self):
return '<Job: %s>' % self.name
class UserJob(models.Model):
user = models.ForeignKey(User)
job = models.ForeignKey(Job)
def __repr__(self):
return '<UserJob: %s %s>' % (self.user.name, self.job.name)
And populate it as follows:
u1 = User.objects.create(name='u1')
u2 = User.objects.create(name='u2')
u3 = User.objects.create(name='u3')
a = Job.objects.create(name='a')
b = Job.objects.create(name='b')
c = Job.objects.create(name='c')
UserJob.objects.create(user=u1, job=a)
UserJob.objects.create(user=u2, job=a)
UserJob.objects.create(user=u2, job=b)
UserJob.objects.create(user=u3, job=a)
UserJob.objects.create(user=u3, job=c)
The following query will return you user 3, which is the only user who has both "Job a" and "Job c":
u = User.objects.filter(userjob__job=a).filter(userjob__job=c)
(or, if you prefer to refer to the jobs by name rather than Job instance):
u = User.objects.filter(userjob__job__name='a').filter(userjob__job__name='c')
You can see how Django is allowing you to traverse the related fields from the User model to the UserJob model with the double underscore notation (the Django docs on this are here: http://docs.djangoproject.com/en/1.2/topics/db/queries/#lookups-that-span-relationships.
Once you get the User object back, you can similarly access the UserJob instances using the relationship properties that Django adds to the model:
u = User.objects.filter(userjob__job__name='a').filter(userjob__job__name='c')
jobs = u.userjob_set.all()
pip install django-filter
Then add django-filter in your install apps:
'django_filters'
Then create a new file like filter.py
import django_filters
from rent.models import Rent
from django.views import View
class RentFilter(django_filters.FilterSet):
class Meta:
model = Rent
fields = [
'name', 'bed_room', 'bath_room', 'rent_location', 'types'
]
After that import RentFilter in your views.py then follow the step:
class BookingFilerListView(View):
def get(self, request):
booking_list = Booking.objects.all()
booking_filer = BookingFilter(request.GET, queryset=booking_list)
template = 'booking/booking_filter.html'
ctx = {'result': booking_filer}
return render(request, template, ctx)
Finally, in your booking_filter.html template look like this:
<form method="get">
<div class="card-body">
<div class="form-group">
<label for="phone_number">Phone Number</label>
<input type="text" class="form-control" placeholder="Phone Number" name="phone_number">
</div>
<div class="form-group">
<label for="transaction_id">Transaction ID</label>
<input type="text" class="form-control" placeholder="Transaction ID" name="transaction_id">
</div>
<div class="form-group">
<label for="booking_date">Booking Date</label>
<input type="date" class="form-control" placeholder="Booking Date" name="booking_date">
</div>
<div class="form-group">
<label for="status">Booking Date</label>
<select class="form-control" name="status" id="status">
<option value="0">PENDING</option>
<option value="1">CANCEL</option>
<option value="2">PROGRESS</option>
<option value="3">DONE</option>
</select>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-success">Filter Booking</button>
<button type="reset" class="btn btn-danger">Reset</button>
</div>
</form>
Print the output in your template bottom:
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Phone Number</th>
<th>Transaction ID</th>
<th>Booking Date</th>
</tr>
</thead>
<tbody>
{% if result %}
{% for obj in result.qs %}
<tr>
<td>{{ obj.phone_number }}</td>
<td>{{ obj.transaction_id }}</td>
<td>{{ obj.booking_date }}</td>
</tr>
{% endfor %}
{% else %}
{% endif %}
</tbody>
</table>