-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add search day-care functionality to dog-owner homepage. #89
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from django import forms | ||
from .models import Area | ||
|
||
|
||
class DayCareSearchForm(forms.Form): | ||
start_date = forms.DateField(required=True, label=' Start date', | ||
widget=forms.widgets.DateInput(attrs={'class': 'form-control', 'type': 'date'})) | ||
end_date = forms.DateField(required=True, label=' End date', | ||
widget=forms.widgets.DateInput(attrs={'class': 'form-control', 'type': 'date'})) | ||
area = forms.ChoiceField(required=False, label='Area', | ||
choices=(Area.choices + [('', 'All'), ]), initial="") | ||
city = forms.CharField(required=False, label='City') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we have a list of cities? a free-text would be tough. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please see #89 (comment) and let me know what you think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you sum it up? I'm not sure I'm following. Anyway free-text can lead to issues (e.g: פתח-תקוה, פתח תקווה, פתח-תקווה, פתח תקוה) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. List of cities seems to me good solution for static-users ,but because all users are static we can control the cities input and just avoid mistakes like that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The UI is an entry point for this app and as such it should perform validation on the input, |
||
name = forms.CharField(required=False, label='Day care name') | ||
price_per_day = forms.IntegerField(required=False, label='Max price') | ||
|
||
def clean(self): | ||
cleaned_data = super().clean() | ||
start_date = cleaned_data.get("start_date") | ||
end_date = cleaned_data.get("end_date") | ||
|
||
if start_date and end_date: | ||
if end_date < start_date: | ||
self._errors['end_date'] = self.error_class(["End date should be greater!"]) | ||
|
||
return cleaned_data |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Generated by Django 4.0.3 on 2022-04-27 13:37 | ||
Yuval-Vino marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import django.core.validators | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('daycare', '0005_image_data_migration'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='daycare', | ||
name='area', | ||
field=models.CharField(blank=True, choices=[('N', 'NORTH'), ('S', 'SOUTH'), ('C', 'CENTER')], | ||
max_length=20, validators=[django.core.validators.MaxLengthValidator]), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,13 +10,19 @@ | |
from django.core.exceptions import ObjectDoesNotExist | ||
|
||
|
||
class Area(models.TextChoices): | ||
North = 'N', 'NORTH' | ||
South = 'S', 'SOUTH' | ||
Center = 'C', 'CENTER' | ||
|
||
|
||
class DayCare(models.Model): | ||
user = models.OneToOneField(User, on_delete=models.CASCADE, default=None, blank=True, null=False, editable=True) | ||
name = models.CharField(max_length=20, blank=True, unique=True, validators=[MaxLengthValidator]) | ||
description = models.TextField(blank=True, null=True) | ||
price_per_day = models.IntegerField(blank=False, null=False, default=0) | ||
capacity = models.IntegerField(null=False, blank=True) | ||
area = models.CharField(max_length=20, blank=True, validators=[MaxLengthValidator]) | ||
area = models.CharField(max_length=20, blank=True, validators=[MaxLengthValidator], choices=Area.choices) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we include that in some of the daycare fixtures? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Area field in create_day_care_user from the confest been updated. |
||
city = models.CharField(max_length=20, blank=True, validators=[MaxLengthValidator]) | ||
address = models.CharField(max_length=50, blank=True, validators=[MaxLengthValidator]) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,57 @@ | ||
{% extends "main/base_template.html" %} | ||
{% load crispy_forms_tags %} | ||
{% load static %} | ||
|
||
{% block stylesheets %} | ||
<link rel="stylesheet" href="{% static 'CSS/dog_owner_homepage.css' %}"> | ||
{% endblock %} | ||
|
||
{% block content %} | ||
<div class="row row-cols-1 g-3 cards"> | ||
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> | ||
{% for daycare in daycares %} | ||
<div class="card"> | ||
<img src="{{ daycare.get_daycare_primary_image_url }}" alt="{{ daycare.name }} image" class="card-img-top"> | ||
<div class="card-body"> | ||
<div style="display: flex; flex-direction: row"> | ||
<h5 class="card-title">{{ daycare.name }}</h5> | ||
|
||
<div class="row"> | ||
<form method='POST' action=''>{% csrf_token %} | ||
<div class="col-4" id="searchBoxArea"> | ||
<span>Search for daycare:</span> | ||
<div class="form-group"> | ||
{{ form.start_date|as_crispy_field }} | ||
</div> | ||
<div class="form-group"> | ||
{{ form.end_date|as_crispy_field }} | ||
</div> | ||
<div class="form-group"> | ||
{{ form.price_per_day|as_crispy_field }} | ||
</div> | ||
<div class="form-group"> | ||
{{ form.area|as_crispy_field }} | ||
</div> | ||
<div class="form-group"> | ||
{{ form.city|as_crispy_field }} | ||
</div> | ||
<div class="form-group"> | ||
{{ form.name|as_crispy_field }} | ||
</div> | ||
<input type="submit" value='Search' style="height:30px;width:70px"> | ||
</div> | ||
</form> | ||
<div class="col-10"> | ||
<span id="searchResult">Found {{ day_care_queryset.count }} results for your dog!</span> | ||
<div class="cards"> | ||
{% for daycare in day_care_queryset %} | ||
<div class="card"> | ||
<img src="{{ daycare.get_daycare_primary_image_url }}" alt="{{ daycare.name }} image" class="card-img-top"> | ||
<div class="card-body"> | ||
<div style="display: flex; flex-direction: row"> | ||
<h5 class="card-title">{{ daycare.name }}</h5> | ||
</div> | ||
<p class="card-text">{{ daycare.area | truncatechars:20 }}</p> | ||
<p class="card-text">{{ daycare.city | truncatechars:20 }}</p> | ||
<p class="card-text">{{ daycare.price_per_day | truncatechars:20 }}</p> | ||
<p class="card-text">{{ daycare.description | truncatechars:35}}</p> | ||
<a href="/daycare/{{ daycare.id }}" class="btn btn-warning">Daycare Profile</a> | ||
</div> | ||
</div> | ||
{% endfor %} | ||
</div> | ||
</div> | ||
<p class="card-text">{{ daycare.description | truncatechars:250 }}</p> | ||
<a href="/daycare/{{ daycare.id }}" class="btn btn-primary">Daycare Profile</a> | ||
</div> | ||
</div> | ||
{% endfor %} | ||
</div> | ||
</div> | ||
|
||
{% endblock %} | ||
{% endblock %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,27 @@ | ||
from django.contrib.auth.decorators import login_required | ||
from django.shortcuts import render | ||
from daycare.models import DayCare | ||
from daycare.forms import DayCareSearchForm | ||
from orders.models import Order | ||
|
||
|
||
@login_required() | ||
def dog_owner_home(request): | ||
form = DayCareSearchForm(request.POST or None) | ||
day_care_queryset = DayCare.objects.all() | ||
if request.method == 'POST': | ||
if form.is_valid(): | ||
filter_day_cares = DayCare.objects.filter(area__startswith=form['area'].value(), | ||
city__icontains=form['city'].value(), | ||
name__icontains=form['name'].value(), | ||
price_per_day__lte=form['price_per_day'].value()) | ||
|
||
available_day_cares = Order.get_all_day_cares_available_on_dates(form['start_date'].value(), | ||
form['end_date'].value()) | ||
day_care_queryset = filter_day_cares.intersection(available_day_cares) | ||
|
||
context = { | ||
'daycares': DayCare.objects.all(), | ||
'day_care_queryset': day_care_queryset, | ||
'form': form | ||
} | ||
return render(request, 'dogowner/dog_owner_homepage.html', context) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
import pytest | ||
from dogowner.models import DogOwner | ||
from daycare.models import DayCare | ||
from orders.models import Order | ||
|
||
|
||
@pytest.mark.django_db | ||
|
@@ -72,8 +74,66 @@ def test_unlogged_user_access_to_homepage(self, client): | |
assert response.status_code == 302 | ||
assert response['Location'] == '/login/?next=/homepage/' | ||
|
||
def test_dog_owner_homepage_is_visible_for_dog_owner(self, client, create_dog_owner_user): | ||
|
||
@pytest.mark.django_db | ||
class TestDogOwnerHomePageView: | ||
def test_dog_owner_homepage_present_all_daycares(self, client, create_dog_owner_user): | ||
client.force_login(user=create_dog_owner_user.user) | ||
response = client.get("/homepage/") | ||
assert response.status_code == 200 | ||
assert list(response.context['daycares']) == list(DayCare.objects.all()) | ||
assert set(response.context['day_care_queryset']) == set(DayCare.objects.all()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this test is ensuring that the page will have a list of all the day cares, maybe it should be mentioned in its name. or in a different test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NP , Fixed by changing the name to " test_dog_owner_homepage_present_all_daycares" |
||
|
||
def test_daycare_not_appears_in_search_after_reduce_capacity_on_dates_to_0(self, client, | ||
create_dog_owner_user, | ||
create_daycare_user): | ||
search_form = {'area': "", | ||
'city': "", | ||
'price_per_day': 100, | ||
'name': "", | ||
'start_date': "2022-05-03", | ||
'end_date': "2022-05-08", | ||
} | ||
daycare_user = create_daycare_user | ||
client.force_login(user=create_dog_owner_user.user) | ||
response = client.post('/homepage/', search_form, follow=True) | ||
day_care_queryset = response.context['day_care_queryset'] | ||
assert daycare_user in day_care_queryset | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would make the test look nicer if the assertion would be separated with newline from the test setup logic (all over this file). |
||
|
||
for _ in range(daycare_user.capacity): | ||
Order.create(dog_owner_id=DogOwner.objects.get(id=1), daycare_id=daycare_user, | ||
start_date="2022-05-02", end_date="2022-08-02", price_per_day=100).approve_order() | ||
response = client.post('/homepage/', search_form, follow=True) | ||
assert daycare_user not in response.context['day_care_queryset'] | ||
|
||
def test_successful_dog_owner_search_for_day_care(self, client, create_dog_owner_user): | ||
client.force_login(user=create_dog_owner_user.user) | ||
search_form = {'area': "C", | ||
'city': "tel aviv", | ||
'price_per_day': 800, | ||
'name': "", | ||
'start_date': "2022-05-03", | ||
'end_date': "2022-05-08", | ||
} | ||
response = client.post('/homepage/', search_form, follow=True) | ||
day_care_queryset = response.context['day_care_queryset'] | ||
available_day_cares = Order.get_all_day_cares_available_on_dates("2022-05-03", "2022-05-08") | ||
filter_day_cares = DayCare.objects.filter(area__startswith='C', | ||
city__icontains="tel aviv", | ||
price_per_day__lte=800) | ||
expected_queryset = available_day_cares.intersection(filter_day_cares) | ||
assert set(day_care_queryset) == set(expected_queryset) | ||
|
||
def test_search_for_day_care_with_start_date_greater_than_end_date_show_error_and_present_all_daycares( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By mentioning |
||
self, client, create_dog_owner_user): | ||
client.force_login(user=create_dog_owner_user.user) | ||
search_form = {'area': "C", | ||
'city': "tel aviv", | ||
'price_per_day': 800, | ||
'name': "", | ||
'start_date': "2022-05-03", | ||
'end_date': "2022-05-01", | ||
} | ||
response = client.post('/homepage/', search_form, follow=True) | ||
day_care_queryset = response.context['day_care_queryset'] | ||
assert set(day_care_queryset) == set(DayCare.objects.all()) | ||
assert response.context['form']._errors['end_date'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Why not using the full name? 🙂