Skip to content
Snippets Groups Projects
Commit d4bfbce6 authored by Valentin Groues's avatar Valentin Groues :eyes:
Browse files

add contact attempts feature - #34

parent def4a6be
No related branches found
No related tags found
1 merge request!32Resolve "Contact attempts"
Pipeline #
Showing
with 362 additions and 118 deletions
......@@ -19,8 +19,8 @@ appointment-import/tmp.sql
*.iml
out
.idea
<<<<<<< HEAD
#coverage tool
.coverage
smash/htmlcov/*
smash/htmlcov/
......@@ -17,6 +17,6 @@ test:
script:
- cp "local_settings_ci.py" "smash/smash/local_settings.py"
- cd smash
- python manage.py makemigrations && python manage.py migrate
- python manage.py makemigrations web && python manage.py migrate
- coverage run --source web manage.py test
- coverage report -m
......@@ -15,7 +15,6 @@ import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
......@@ -100,7 +99,6 @@ USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
......
from datetime import datetime
import datetime
from django import forms
from django.forms import ModelForm, Form
from django.utils.dates import MONTHS
from models import Subject, Worker, Appointment, Visit, AppointmentType
from models import Subject, Worker, Appointment, Visit, AppointmentType, ContactAttempt
from models.constants import SUBJECT_TYPE_CHOICES
"""
Possible redundancy, but if need arises, contents of forms can be easily customized
"""
CURRENT_YEAR = datetime.now().year
CURRENT_YEAR = datetime.datetime.now().year
YEAR_CHOICES = tuple(range(CURRENT_YEAR, CURRENT_YEAR - 120, -1))
FUTURE_YEAR_CHOICES = tuple(range(CURRENT_YEAR, CURRENT_YEAR + 5, 1))
DATEPICKER_DATE_ATTRS = {
......@@ -79,6 +79,7 @@ class SubjectAddForm(ModelForm):
validate_subject_nd_number(self)
def get_new_screening_number(screening_number_prefix):
result_number = 0
subjects = Subject.objects.filter(screening_number__contains=screening_number_prefix)
......@@ -95,12 +96,14 @@ def get_new_screening_number(screening_number_prefix):
return screening_number_prefix + str(result_number + 1).zfill(3)
def get_prefix_screening_number(user):
prefix_screening_number = ''
if (user is not None) and (user.screening_number_prefix is not None) and (user.screening_number_prefix != ""):
prefix_screening_number = user.screening_number_prefix + "-"
return prefix_screening_number
class SubjectDetailForm(ModelForm):
class Meta:
model = Subject
......@@ -268,6 +271,29 @@ class VisitAddForm(ModelForm):
self.add_error('datetime_end', "End date must be after start date")
class ContactAttemptForm(ModelForm):
datetime_when = forms.DateTimeField(label='Contact on (YYYY-MM-DD HH:MM)',
widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS)
)
class Meta:
model = ContactAttempt
fields = '__all__'
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
if user is None:
raise TypeError("User not defined")
self.user = Worker.get_by_user(user)
if self.user is None:
raise TypeError("Worker not defined for: " + user.username)
subject = kwargs.pop('subject', None)
super(ContactAttemptForm, self).__init__(*args, **kwargs)
self.fields['subject'].initial = subject.id
self.fields['subject'].disabled = True
self.fields['worker'].initial = self.user
class KitRequestForm(Form):
start_date = forms.DateField(label="From date",
widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"),
......@@ -286,7 +312,7 @@ class StatisticsForm(Form):
visit_choices = kwargs['visit_choices']
month = kwargs['month']
year = kwargs['year']
now = datetime.now()
now = datetime.datetime.now()
year_now = now.year
number_of_years_for_statistics = year_now - START_YEAR_STATISTICS + 2
......
......@@ -17,6 +17,7 @@ from holiday import Holiday
from item import Item
from language import Language
from subject import Subject
from contact_attempt import ContactAttempt
def get_current_year():
......@@ -24,4 +25,4 @@ def get_current_year():
__all__ = [FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, Subject,
Visit, Worker]
Visit, Worker, ContactAttempt]
......@@ -5,7 +5,6 @@ from django.db import models
class Availability(models.Model):
class Meta:
app_label = 'web'
db_table = 'web_avaibility'
person = models.ForeignKey("web.Worker", on_delete=models.CASCADE,
verbose_name='Worker'
......
......@@ -13,3 +13,17 @@ SUBJECT_TYPE_CHOICES = {
}
APPOINTMENT_TYPE_DEFAULT_COLOR = '#cfc600'
APPOINTMENT_TYPE_DEFAULT_FONT_COLOR = '#00000'
CONTACT_TYPES_EMAIL = 'E'
CONTACT_TYPES_PHONE = 'P'
CONTACT_TYPES_SMS = 'S'
CONTACT_TYPES_FAX = 'X'
CONTACT_TYPES_FACE2FACE = 'F'
CONTACT_TYPES_CHOICES = (
(CONTACT_TYPES_EMAIL, 'Email'),
(CONTACT_TYPES_FACE2FACE, 'Face to face'),
(CONTACT_TYPES_FAX, 'Fax'),
(CONTACT_TYPES_PHONE, 'Phone'),
(CONTACT_TYPES_SMS, 'SMS'),
)
# coding=utf-8
from django.db import models
from constants import CONTACT_TYPES_CHOICES, CONTACT_TYPES_PHONE
__author__ = 'Valentin Grouès'
class ContactAttempt(models.Model):
subject = models.ForeignKey("web.Subject",
verbose_name='Subject'
)
worker = models.ForeignKey("web.Worker", null=True,
verbose_name='Worker'
)
type = models.CharField(max_length=2, default=CONTACT_TYPES_PHONE, choices=CONTACT_TYPES_CHOICES)
datetime_when = models.DateTimeField(verbose_name="Contact on", help_text='When did the contact occurred?')
success = models.BooleanField(default=False)
comment = models.TextField(max_length=1024, null=True, blank=True)
def __str__(self):
return "%s %s" % (self.subject, self.worker)
def __unicode__(self):
return "%s %s" % (self.subject, self.worker)
......@@ -18,7 +18,7 @@
{% block page_header %}New appointment{% endblock page_header %}
{% block page_description %}{% endblock page_description %}
{% block title %}{{ block.super }} - Add new appoitnment{% endblock %}
{% block title %}{{ block.super }} - Add new appointment{% endblock %}
{% block breadcrumb %}
{% include "appointments/breadcrumb.html" %}
......
{% extends "_base.html" %}
{% load static %}
{% load filters %}
{% block styles %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'AdminLTE/plugins/awesomplete/awesomplete.css' %}"/>
{% include "includes/datepicker.css.html" %}
{% endblock styles %}
{% block ui_active_tab %}'subjects'{% endblock ui_active_tab %}
{% block page_header %}New contact attempt{% endblock page_header %}
{% block page_description %}{% endblock page_description %}
{% block title %}{{ block.super }} - Add new contact attempt{% endblock %}
{% block breadcrumb %}
{% include "subjects/breadcrumb.html" %}
{% endblock breadcrumb %}
{% block maincontent %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="box box-success">
<div class="box-header with-border">
<h3 class="box-title">Enter contact attempt details</h3>
</div>
<form method="post" action="" class="form-horizontal">
{% csrf_token %}
<div class="box-body">
{% for field in form %}
<div class="form-group {% if field.errors %}has-error{% endif %}">
<label class="col-sm-4 col-lg-offset-1 col-lg-2 control-label">
{{ field.label }}
</label>
<div class="col-sm-8 col-lg-4">
{{ field|add_class:'form-control' }}
</div>
{% if field.errors %}
<span class="help-block">
{{ field.errors }}
</span>
{% endif %}
</div>
{% endfor %}
</div><!-- /.box-body -->
<div class="box-footer">
<div class="col-sm-6">
<button type="submit" class="btn btn-block btn-success">Add</button>
</div>
<div class="col-sm-6">
<a href="{% url 'web.views.subject_edit' subject_id %}"
class="btn btn-block btn-default">Cancel</a>
</div>
</div><!-- /.box-footer -->
</form>
</div>
</div>
</div>
{% endblock %}
{% endblock maincontent %}
{% block scripts %}
{{ block.super }}
<script src="{% static 'AdminLTE/plugins/awesomplete/awesomplete.min.js' %}"></script>
{% include "includes/datetimepicker.js.html" %}
{% endblock scripts %}
......@@ -23,53 +23,103 @@
{% block maincontent %}
{% block content %}
<div class="box box-info">
<div class="box-header with-border">
<a href="{% url 'web.views.subjects' %}" class="btn btn-block btn-default" onclick="history.back()">Go
back (without changes)</a>
<div class="row">
<p class="col-lg-3 pull-left">
<a href="{% url 'web.views.subjects' %}" class="btn btn-block btn-default"
onclick="history.back()">Go
back (discard changes)</a>
</p>
<p class="col-md-2 pull-right">
<a href="{% url 'web.views.subject_visit_details' subject.id %}" type="button"
class="btn btn-block btn-default">Subject's visits</a>
</div>
{% comment %} <div class="box-header with-border">
<h3 class="box-title">Details of subject</h3>
</div>{% endcomment %}
<form method="post" action="" class="form-horizontal">
{% csrf_token %}
<div class="box-body">
<div class="col-md-12">
{% for field in form %}
<div class="col-md-6 form-group {% if field.errors %}has-error{% endif %}">
<label for="{# TODO #}" class="col-sm-4 control-label">
{{ field.label }}
</label>
<div class="col-sm-8">
{{ field|add_class:'form-control' }}
</div>
{% if field.errors %}
<span class="help-block"> {{ field.errors }} </span>
{% endif %}
</div>
{% endfor %}
</p>
</div>
<div class="row">
<div class="col-md-12">
<div class="box box-success">
<div class="box-header with-border">
<h3>Subject details</h3>
</div>
</div><!-- /.box-body -->
<div class="box-footer">
<div class="col-sm-6">
<button type="submit" class="btn btn-block btn-success">Save</button>
<div class="box-body">
<div class="col-md-12">
<form method="post" action="" class="form-horizontal">
{% csrf_token %}
{% for field in form %}
<div class="col-md-6 form-group {% if field.errors %}has-error{% endif %}">
<label for="{# TODO #}" class="col-sm-4 control-label">
{{ field.label }}
</label>
<div class="col-sm-8">
{{ field|add_class:'form-control' }}
</div>
{% if field.errors %}
<span class="help-block"> {{ field.errors }} </span>
{% endif %}
</div>
{% endfor %}
</form>
</div>
</div><!-- /.box-body -->
<div class="box-footer">
<div class="col-sm-6">
<button type="submit" class="btn btn-block btn-success">Save</button>
</div>
<div class="col-sm-6">
<a href="{% url 'web.views.subjects' %}" class="btn btn-block btn-default"
onclick="history.back()">Cancel</a>
</div>
</div><!-- /.box-footer -->
</div><!-- /.box -->
</div><!-- /.col-md-12 -->
</div><!-- /.row -->
<div class="row">
<div class="col-lg-6">
<div class="box box-success">
<div class="box-header with-border">
<h3>Contact attempts <a title="add a new contact attempt"
id="add-contact-attempt"
href="{% url 'web.views.contact_add' subject.id %}" class="text-primary"
><i class="fa fa-plus-circle text-success"></i></a></h3>
</div>
<div class="col-sm-6">
<a href="{% url 'web.views.subjects' %}" class="btn btn-block btn-default"
onclick="history.back()">Cancel</a>
<div class="box-body">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th class="text-center">When</th>
<th class="text-center">Who</th>
<th class="text-center">Type</th>
<th class="text-center">Success</th>
<th class="text-center">Comment</th>
</tr>
</thead>
<tbody>
{% for contact_attempt in contact_attempts %}
<tr>
<td>{{ contact_attempt.datetime_when }}</td>
<td>{{ contact_attempt.worker }}</td>
<td class="text-center">{{ contact_attempt.get_type_display }}</td>
<td class="text-center">
<i class="fa {% if contact_attempt.success %}fa-check text-success{% else %}fa-times text-danger{% endif %}"></i>
</td>
<td>{{ contact_attempt.comment }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div><!-- /.box-footer -->
</form>
</div>
</div>
</div>
<div class="modal modal-danger fade" id="confirm-dead-resigned-mark-dialog" tabindex="-1" role="dialog">
......
from django.contrib.auth.models import User
from django.test import Client
from django.test import TestCase
from functions import create_worker
class LoggedInTestCase(TestCase):
def setUp(self):
self.client = Client()
username = 'piotr'
password = 'top_secret'
self.user = User.objects.create_user(
username=username, email='jacob@bla', password=password)
self.client.login(username=username, password=password)
class LoggedInWithWorkerTestCase(LoggedInTestCase):
def setUp(self):
super(LoggedInWithWorkerTestCase, self).setUp()
self.worker = create_worker(self.user)
# coding=utf-8
import datetime
import json
from django.contrib.auth.models import User
from django.test import TestCase
from django.test import Client
from django.urls import reverse
from web.models import Visit
from web.api_views import cities
from web.tests.functions import create_subject, create_worker, create_appointment_type
from web.tests.functions import create_subject, create_appointment_type
from . import LoggedInWithWorkerTestCase
__author__ = 'Piotr Gawron'
class TestApi(TestCase):
class TestApi(LoggedInWithWorkerTestCase):
def setUp(self):
super(TestApi, self).setUp()
self.subject = create_subject()
self.client = Client()
username = 'piotr'
password = 'top_secret'
self.user = User.objects.create_user(
username=username, email='jacob@bla', password=password)
self.worker = create_worker(self.user)
self.client.login(username=username, password=password)
def test_cities(self):
city_name = "some city"
......@@ -130,7 +119,7 @@ class TestApi(TestCase):
found = False
for type in appointment_types:
if type['type']==type_name:
if type['type'] == type_name:
found = True
self.assertTrue(found)
from django.test import TestCase
from functions import create_subject, create_appointment
from functions import create_visit
from web.models import Appointment
from web.models import Visit
from functions import create_appointment
class AppointmentModelTests(TestCase):
......
......@@ -27,8 +27,8 @@ class SubjectModelTests(TestCase):
subject.mark_as_resigned()
appointment_status = Appointment.objects.filter(id=appointment.id)[0].status
visit_finsihed = Visit.objects.filter(id=visit.id)[0].is_finished
visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished
self.assertTrue(subject.resigned)
self.assertTrue(visit_finsihed)
self.assertTrue(visit_finished)
self.assertEquals(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status)
import datetime
from django.contrib.auth.models import User
from django.test import Client
from django.test import TestCase
from django.urls import reverse
from functions import create_subject, create_visit, create_appointment, create_worker
from web.forms import AppointmentEditForm, SubjectEditForm
from web.models import Appointment, Subject
from . import LoggedInTestCase
class AppointmentsViewTests(TestCase):
def setUp(self):
self.client = Client()
username = 'piotr'
password = 'top_secret'
self.user = User.objects.create_user(
username=username, email='jacob@bla', password=password)
self.client.login(username=username, password=password)
class AppointmentsViewTests(LoggedInTestCase):
def test_appointments_list_request(self):
response = self.client.get(reverse('web.views.appointments'))
self.assertEqual(response.status_code, 200)
......
import datetime
from django.urls import reverse
from django.utils import timezone
from functions import create_subject
from web.models import ContactAttempt
from web.models.constants import CONTACT_TYPES_EMAIL
from . import LoggedInWithWorkerTestCase
class ContactAttemptViewTests(LoggedInWithWorkerTestCase):
def test_contact_attempt_add_get(self):
subject = create_subject()
response = self.client.get(reverse('web.views.contact_add', kwargs={'subject_id': subject.id}))
self.assertContains(response, 'selected">{}'.format(self.worker), 1)
self.assertContains(response, 'selected">{}'.format(subject), 1)
def test_contact_attempt_add_post_valid(self):
subject = create_subject()
self.assertEqual(0, ContactAttempt.objects.filter(subject=subject).count())
now = datetime.datetime.now()
now_aware = timezone.make_aware(now, timezone.get_default_timezone())
contact_type = CONTACT_TYPES_EMAIL
comment = "this is a comment"
form_data = {'datetime_when': now, 'worker': self.worker.id, 'type': contact_type, 'comment': comment}
response = self.client.post(
reverse('web.views.contact_add', kwargs={'subject_id': subject.id}), data=form_data)
# check correct redirection to suject edit page
self.assertRedirects(response, reverse('web.views.subject_edit', kwargs={'id': subject.id}))
contact_attempts = ContactAttempt.objects.filter(subject=subject).all()
self.assertEqual(1, len(contact_attempts))
contact_attempt = contact_attempts[0]
self.assertEqual(now_aware, contact_attempt.datetime_when)
self.assertEqual(contact_type, contact_attempt.type)
self.assertEqual(subject, contact_attempt.subject)
self.assertEqual(self.worker, contact_attempt.worker)
self.assertEqual(comment, contact_attempt.comment)
self.assertFalse(contact_attempt.success)
# follow redirect to check if the new contact attempt is correctly listed
response = self.client.get(response.url)
self.assertContains(response, comment, 1)
def test_contact_attempt_add_post_invalid(self):
subject = create_subject()
self.assertEqual(0, ContactAttempt.objects.filter(subject=subject).count())
contact_type = CONTACT_TYPES_EMAIL
comment = "this is a comment"
form_data = {'type': contact_type, 'comment': comment}
response = self.client.post(
reverse('web.views.contact_add', kwargs={'subject_id': subject.id}), data=form_data)
self.assertContains(response, "This field is required", 2)
self.assertEqual(0, ContactAttempt.objects.filter(subject=subject).count())
import datetime
from django.test import TestCase, RequestFactory
from django.urls import reverse
from functions import create_user, create_appointment_type, create_appointment
from functions import create_appointment_type, create_appointment
from web.models import Item, Appointment
from web.views.kit import kit_requests
from web.views.notifications import get_today_midnight_date
from . import LoggedInTestCase
class ViewFunctionsTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = create_user()
class ViewFunctionsTests(LoggedInTestCase):
def test_kit_requests(self):
request = self.factory.get(reverse('web.views.kit_requests'))
request.user = self.user
response = kit_requests(request)
response = self.client.get(reverse('web.views.kit_requests'))
self.assertEqual(response.status_code, 200)
def test_kit_requests_2(self):
......@@ -32,9 +25,7 @@ class ViewFunctionsTests(TestCase):
appointment.appointment_types.add(appointment_type)
appointment.save()
request = self.factory.get(reverse('web.views.kit_requests'))
request.user = self.user
response = kit_requests(request)
response = self.client.get(reverse('web.views.kit_requests'))
self.assertEqual(response.status_code, 200)
self.assertTrue(item_name in response.content)
......@@ -52,9 +43,7 @@ class ViewFunctionsTests(TestCase):
appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED
appointment.save()
request = self.factory.get(reverse('web.views.kit_requests'))
request.user = self.user
response = kit_requests(request)
response = self.client.get(reverse('web.views.kit_requests'))
self.assertEqual(response.status_code, 200)
self.assertFalse(item_name in response.content)
......@@ -71,9 +60,7 @@ class ViewFunctionsTests(TestCase):
appointment.appointment_types.add(appointment_type)
appointment.save()
request = self.factory.get(reverse('web.views.kit_requests'))
request.user = self.user
response = kit_requests(request)
response = self.client.get(reverse('web.views.kit_requests'))
self.assertEqual(response.status_code, 200)
self.assertTrue(item_name in response.content)
# coding=utf-8
from django.contrib import auth as django_auth
from django.test import Client
from django.test import TestCase
from django.urls import reverse
......@@ -14,11 +15,28 @@ class TestLoginView(TestCase):
password = 'top_secret'
username = user.username
login_url = reverse('web.views.login')
self.assertFalse(django_auth.get_user(self.client).is_authenticated())
response = self.client.post(login_url, data={'username': username, 'password': password}, follow=True)
self.assertEqual(200, response.status_code)
self.assertTrue(django_auth.get_user(self.client).is_authenticated())
worker = Worker.get_by_user(user)
self.assertIsNotNone(worker)
worker.last_name = 'Grouès'
worker.save()
response = self.client.post(login_url, data={'username': username, 'password': password}, follow=True)
self.assertEqual(200, response.status_code)
def test_login_failed(self):
self.client = Client()
user = create_user()
username = user.username
login_url = reverse('web.views.login')
response = self.client.post(login_url, data={'username': username, 'password': 'wrong_password'}, follow=False)
self.assertEqual(302, response.status_code)
self.assertEqual('/login?error=login_failed', response.url)
self.assertFalse(django_auth.get_user(self.client).is_authenticated())
def test_logout(self):
self.test_login()
self.client.get(reverse('web.views.logout'))
self.assertFalse(django_auth.get_user(self.client).is_authenticated())
import datetime
from django.contrib.auth.models import User
from django.test import TestCase, RequestFactory
from functions import create_appointment, create_location, create_worker, create_appointment_type
from functions import create_subject
from functions import create_visit
......@@ -21,16 +18,11 @@ from web.views.notifications import \
get_today_midnight_date, \
get_unfinished_appointments, \
get_unfinished_appointments_count, \
get_unfinished_visits, get_active_visits_with_missing_appointments
get_unfinished_visits
from . import LoggedInTestCase
class NotificationViewTests(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='piotr', email='jacob@bla', password='top_secret')
class NotificationViewTests(LoggedInTestCase):
def test_get_exceeded_visit_notifications_count(self):
original_notification = get_visits_without_appointments_count(self.user)
......@@ -85,7 +77,7 @@ class NotificationViewTests(TestCase):
appointment = create_appointment(visit)
appointment.appointment_types.add(appointment_type)
appointment.status=Appointment.APPOINTMENT_STATUS_FINISHED
appointment.status = Appointment.APPOINTMENT_STATUS_FINISHED
appointment.save()
notification = get_visits_with_missing_appointments_count(self.user)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment