diff --git a/requirements.txt b/requirements.txt index 01f45824f0cbf1d4f885b222807a543dcaf2de59..430b5f8755f21bbc65da2b720da97812fb6b0288 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,7 @@ pyexcel==0.5.3 pycurl==7.43.0 django-stronghold==0.2.9 timeout-decorator==0.4.0 +Unidecode==1.0.22 +Faker==0.9.2 +pandas==0.23.4 +numpy==1.15.2 \ No newline at end of file diff --git a/smash/create_dummy_data.py b/smash/create_dummy_data.py new file mode 100644 index 0000000000000000000000000000000000000000..9379af3675344dee833a5191c34c0f63e59ef24a --- /dev/null +++ b/smash/create_dummy_data.py @@ -0,0 +1,715 @@ +from django.conf import settings +from django.core.files import File # you need this somewhere +import urllib +from django.core.files.uploadedfile import SimpleUploadedFile +import django +import datetime +from django.utils import timezone +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "smash.settings") +django.setup() +from django.contrib.auth.models import User +# models (please add in both lines) +from web.models import StudySubject, Availability, Visit, Appointment, AppointmentType, AppointmentTypeLink, Study, Subject, Worker, Location, Language, Country, WorkerStudyRole, Item, FlyingTeam, Room, MailTemplate +from smash.local_settings import MEDIA_ROOT +from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, REDCAP_BASE_URL_CONFIGURATION_TYPE, \ + SEX_CHOICES_MALE, SEX_CHOICES_FEMALE, SUBJECT_TYPE_CHOICES_CONTROL, SUBJECT_TYPE_CHOICES_PATIENT, CONTACT_TYPES_PHONE, \ + MONDAY_AS_DAY_OF_WEEK, COUNTRY_AFGHANISTAN_ID, VOUCHER_STATUS_NEW, GLOBAL_STUDY_ID, DEFAULT_LOCALE_NAME +from web.models.constants import MAIL_TEMPLATE_CONTEXT_APPOINTMENT, MAIL_TEMPLATE_CONTEXT_VISIT, \ + MAIL_TEMPLATE_CONTEXT_SUBJECT, MAIL_TEMPLATE_CONTEXT_VOUCHER +from web.models.worker_study_role import ROLE_CHOICES_PROJECT_MANAGER, ROLE_CHOICES_SECRETARY, ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER, ROLE_CHOICES_TECHNICIAN, ROLE_CHOICES_PSYCHOLOGIST, ROLE_CHOICES_NURSE +from web.tests.functions import get_resource_path + +from collections import defaultdict +import logging +logger = logging.getLogger(__name__) + +from web.views.notifications import get_today_midnight_date +from faker.providers import BaseProvider, color +from numpy.random import choice +from faker import Faker +import platform +import tempfile +import unidecode +from shutil import copyfile + + +class smashProvider(BaseProvider): + + __provider__ = 'smash' + __lang__ = 'fr_FR' + fake = Faker() + fake.seed(4321) + + specialists = ['Psychiatrist', 'Radiologist', 'Immunologist', 'Anesthesiologist', + 'Surgeon', 'Pediatrician', 'Neurologist', 'Medical examiner'] + units = ['LHI', 'PCR', 'LCSB', 'ACO', 'PLI', 'LRSU'] + workerRoles = [ROLE_CHOICES_PROJECT_MANAGER, ROLE_CHOICES_SECRETARY, ROLE_CHOICES_DOCTOR, + ROLE_CHOICES_TECHNICIAN, ROLE_CHOICES_PSYCHOLOGIST, ROLE_CHOICES_NURSE] + + #towns and cities + near_fr_towns = ['Metz', 'Audun-le-Tiche', 'Thionville'] + near_de_towns = ['Trier'] + near_be_towns = ['Aubange', 'Arlon'] + luxtowns = ['Luxembourg City', 'Esch-sur-Alzette', 'Differdange', 'Dudelange', 'Ettelbruck', + 'Diekirch', 'Wiltz', 'Rumelange', 'Echternach', 'Grevenmacher', 'Remich', 'Vianden'] + + # if you add or remove cities, be careful and change the probabilities as well + # here the probs ensure the luxembourgish towns have 0.8 prob, and so on... + cities = luxtowns + near_fr_towns + near_de_towns + near_be_towns + city_prob = [0.8 / len(luxtowns)] * len(luxtowns) + \ + [0.1 / len(near_fr_towns)] * len(near_fr_towns) + \ + [0.05 / len(near_de_towns)] * len(near_de_towns) + \ + [0.05 / len(near_be_towns)] * len(near_be_towns) + + alreadyCreatedLocations = {} + # languages + language_base_dir = 'web/static/flags/' + language_flags = { + 'French': 'FR.png', + 'English': 'GB.png', + 'Spanish': 'ES.png', + 'Portuguese': 'PT.png', + 'Luxembourgish': 'LU.png', + 'German': 'DE.png', + 'Dutch': 'NL.png' + } + language_locale = { + 'French': 'fr_FR', + 'Luxembourgish': 'lb_LU', + 'Spanish': 'es_ES', + 'English': 'en_GB', + 'Portuguese': 'pt_PT', + 'German': 'de_DE', + 'Dutch': 'nl_NL' + } + languages = ['English', 'Luxembourgish', 'French', + 'German', 'Portuguese', 'Spanish', 'Dutch'] + languages_prob = [0.25, 0.2, 0.16, 0.16, 0.13, 0.05, 0.05] + alreadyCreatedLanguages = {} + # items + items = ['Saliva', 'Blood', 'Liquid Biopsy', + 'X-Ray Examination', 'Urine', 'Tissue Biopsy', 'Skin sample'] + alreadyCreatedItems = {} + # flying teams + places = ['Belval', 'Belvaux', 'Arlon', 'Metz'] + alreadyCreatedFlyingTeams = {} + # countries + countries = ['France', 'Luxembourg', 'Germamny', 'Belgium'] + alreadyCreatedCountries = {} + # screening number + screening_number_ctrs = defaultdict(int) + # template file + template_file = get_resource_path('upcoming_appointment_FR.docx') + template_context = [MAIL_TEMPLATE_CONTEXT_APPOINTMENT, MAIL_TEMPLATE_CONTEXT_VOUCHER, + MAIL_TEMPLATE_CONTEXT_VISIT, MAIL_TEMPLATE_CONTEXT_SUBJECT] + # subjects + alreadyCreatedSubjects = [] + alreadyCreatedStudySubjects = [] + # appointment type + alreadyCreatedAppointmentTypes = [] + # workers + alreadyCreatedWorkers = [] + # rooms + alreadyCreatedRooms = [] + # nd_number_ctr + nd_number_ctr = 0 + + def getLuxembourgTown(self): + return self.fake.word(ext_word_list=self.luxtowns) + + def getFrenchTown(self): + return self.fake.word(ext_word_list=self.near_fr_towns) + + def getBelgiumTown(self): + return self.fake.word(ext_word_list=self.near_be_towns) + + def getDeutschTown(self): + return self.fake.word(ext_word_list=self.near_de_towns) + + def getWorkerRole(self): + return self.fake.word(ext_word_list=self.workerRoles) + + # availability + def createSmashAvailabilities(self): + for worker in self.alreadyCreatedWorkers: + for weekday in set(choice(range(1, 6), 4)): + availability = self.createSmashAvailability( + worker=worker, day_number=weekday) + + def createSmashAvailability(self, worker=None, day_number=None, available_from=None, available_till=None): + if worker is None: + worker = choice(self.alreadyCreatedWorkers) + if day_number is None: + day_number = choice(range(1, 6)) + if available_from is None: + available_from = '{}:00'.format(choice(range(8, 10))) + if available_till is None: + available_till = '{}:00'.format(choice(range(13, 18))) + + availability, _ = Availability.objects.update_or_create(person=worker, + day_number=day_number, + available_from=available_from, + available_till=available_till) + return availability + + # appointments + def createSmashAppointments(self): + today = timezone.now() + # create first visit, in between -2y and -1y from now and first + # appointment + for studySubject in self.alreadyCreatedStudySubjects: + # date_between does not support months so we use days + visit_start_date = fake.date_between( + start_date='-1y', end_date='-334d') - datetime.timedelta(days=-31) + visit_end_date = visit_start_date + datetime.timedelta(days=31) + visit = self.createSmashVisit(studySubject=studySubject, datetime_begin=visit_start_date, + datetime_end=visit_end_date, is_finished=True) + appointment_types = self.getAlreadyCreatedAppointmentTypes() + visit.appointment_types.set(appointment_types) + # appointment + duration = sum([app.default_duration for app in appointment_types]) + appointment_start_date = fake.date_time_between(start_date=visit_start_date, end_date=visit_end_date, + tzinfo=today.tzinfo) + hour = choice(range(9, 18)) + minute = choice(range(0, 30, 5)) + # ensure the time is between office hours + appointment_start_date = appointment_start_date.replace( + hour=hour, minute=minute, tzinfo=today.tzinfo) + + room = choice(self.alreadyCreatedRooms) + appointment = self.createSmashAppointment(visit=visit, datetime_when=appointment_start_date, + length=duration, appointment_types=appointment_types, room=room, status=Appointment.APPOINTMENT_STATUS_FINISHED) + + def createSmashAppointment(self, visit=None, datetime_when=None, location=None, length=30, + status=Appointment.APPOINTMENT_STATUS_SCHEDULED, appointment_types=None, room=None, worker_assigned=None): + if visit is None: + visit = self.createSmashVisit() + if datetime_when is None: + datetime_when = get_today_midnight_date() + if location is None: + location = self.getAlreadyCreatedSmashLocation() + if room is None: + room = choice(self.alreadyCreatedRooms) + if worker_assigned is None: + worker_assigned = choice(self.alreadyCreatedWorkers) + + appointment, _ = Appointment.objects.update_or_create( + visit=visit, length=length, location=location, worker_assigned=worker_assigned, + status=status, datetime_when=datetime_when, room=room) + + if appointment_types is None: + appointment_types = self.getAlreadyCreatedAppointmentTypes() + + # create connection for each appointment type + when = datetime_when + for appointment_type in appointment_types: + worker = choice(self.alreadyCreatedWorkers) + when = when + \ + datetime.timedelta(minutes=appointment_type.default_duration) + app_type_link = AppointmentTypeLink(appointment=appointment, date_when=when, + appointment_type=appointment_type, worker=worker) + + return appointment + + # visit + def createSmashVisit(self, datetime_begin=None, datetime_end=None, studySubject=None, is_finished=False, visit_number=1): + if datetime_begin is None: + datetime_begin = get_today_midnight_date() + datetime.timedelta(days=-31) + if datetime_end is None: + datetime_end = get_today_midnight_date() + datetime.timedelta(days=31) + if studySubject is None: + studySubject = choice(self.alreadyCreatedStudySubjects) + + visit, _ = Visit.objects.update_or_create(datetime_begin=datetime_begin, datetime_end=datetime_end, + subject=studySubject, is_finished=is_finished, visit_number=visit_number) + return visit + + # AppointmentTypes + def getAlreadyCreatedAppointmentTypes(self, n=3): + return set(choice(self.alreadyCreatedAppointmentTypes, n)) + + def createSmashAppointmentTypes(self, n_max=5): + return [self.createSmashAppointmentType() for _ in xrange(n_max)] + + def createSmashAppointmentType(self, code=None, default_duration=10, rest_time=5, description=None): + if code is None: + code = self.fake.word(ext_word_list=['C', 'A', 'N', 'P']) + if description is None: + description = self.fake.word( + ext_word_list=['Examination', 'Analysis', 'Questions', 'Test']) + equipment = self.getSmashAlreadyCreatedItems() + appointmentType, _ = AppointmentType.objects.update_or_create(code=code, rest_time=rest_time, + default_duration=default_duration, description=description) + + appointmentType.required_equipment.set(equipment) + appointmentType.required_worker = choice( + ['DOCTOR', 'NURSE', 'PSYCHOLOGIST', 'ANY']) + appointmentType.save() + + self.alreadyCreatedAppointmentTypes.append(appointmentType) + + return appointmentType + + # mail template + def createSmashMailTemplates(self): + ''' + Returns a list of template objects + ''' + languages = self.getAlreadyCreatedSmashLanguages() + templates = [] + for context in self.template_context: + for language in languages: + template = self.createSmashMailTemplate( + language=language, context=context) + templates.append(template) + return templates + + def createSmashMailTemplate(self, name=None, language=None, context=MAIL_TEMPLATE_CONTEXT_APPOINTMENT, template_file=None): + if language is None: + language = self.getAlreadyCreatedSmashLanguages()[0] + if name is None: + name = '{} Template'.format(language.name) + if template_file is None: + template_file = self.template_file + basename = os.path.basename(template_file) + dst = os.path.join(MEDIA_ROOT, basename) + copyfile(template_file, dst) + template, _ = MailTemplate.objects.update_or_create( + name=name, language=language, context=context, template_file=dst) + return template + + # flying teams + def createSmashFlyingTeams(self): + return [self.createSmashFlyingTeam(place=place) for place in self.places] + + def createSmashFlyingTeam(self, place=None): + if place is None: + place = self.fake.word(ext_word_list=self.places) + flying_team, _ = FlyingTeam.objects.update_or_create(place=place) + + self.alreadyCreatedFlyingTeams[place] = flying_team + + return flying_team + + # item + def createSmashItems(self): + ''' + Returns a list of Item objects + ''' + return [self.createSmashItem(name=item) for item in self.items] + + def createSmashItem(self, name=None, is_fixed=None, disposable=None): + ''' + Returns an Item object + ''' + if name is None: + name = self.fake.word(ext_word_list=self.items) + + if is_fixed is None: + is_fixed = self.fake.boolean(chance_of_getting_true=75) + + if disposable is None: + disposable = self.fake.boolean(chance_of_getting_true=75) + + item, _ = Item.objects.update_or_create( + name=name, is_fixed=is_fixed, disposable=disposable) + + self.alreadyCreatedItems[name] = item + + return item + + def getSmashAlreadyCreatedItems(self, max_n=3): + if len(self.alreadyCreatedItems.keys()) == 0: + self.createSmashItems() + + keys = set(choice(self.alreadyCreatedItems.keys(), max_n)) + return [self.alreadyCreatedItems[key] for key in keys] + + # room + def createSmashRooms(self, n=4): + return [self.createSmashRoom() for _ in xrange(n)] + + def createSmashRoom(self, equipment=None, owner=None, address=None, city=None, room_number=None, floor=None, is_vehicle=None): + ''' + equipment: List of Item objects: + owner: string: + address: string: + city: string: + room_number: int: + floor: int: + is_vehicle: boolean: + ''' + if equipment is None: + item = self.fake.word( + ext_word_list=self.alreadyCreatedItems.keys()) + equipment = self.getSmashAlreadyCreatedItems() + + if owner is None: + owner = self.fake.first_name() + + if address is None: + address = '{}, {} {}'.format(self.fake.word( + ext_word_list=self.cities), self.fake.random_digit_not_null_or_empty(), self.fake.street_name()) + address = address.replace('\n', ' ') + + if city is None: + city = self.fake.word(ext_word_list=self.cities) + + if room_number is None: + room_number = choice(range(10), 1)[0] # choice returns a list + + if floor is None: + floor = choice(range(10), 1)[0] + + if is_vehicle is None: + is_vehicle = self.fake.boolean(chance_of_getting_true=25) + + dicc = {'owner': owner, 'city': city, 'address': address, + 'floor': floor, 'room_number': room_number, 'is_vehicle': is_vehicle} + room, _ = Room.objects.update_or_create(**dicc) + room.equipment.set(equipment) + room.save() + + self.alreadyCreatedRooms.append(room) + + return room + + # country + def getAlreadyCreatedSmashCountry(self): + if len(self.alreadyCreatedCountries) == 0: + self.createSmashCountries() + + name = self.fake.word(ext_word_list=self.countries) + return self.alreadyCreatedCountries[name] + + def createSmashCountries(self): + return [self.createSmashCountry(name=country) for country in self.countries] + + def createSmashCountry(self, name=None, order=0): + if name is None: + name = self.fake.word(ext_word_list=self.countries) + country, _ = Country.objects.update_or_create(name=name, order=order) + self.alreadyCreatedCountries[name] = country + return country + + def createSmashScreeningNumber(self, prefix): + self.screening_number_ctrs[prefix] += 1 + return '{}-{:03}'.format(prefix, self.screening_number_ctrs[prefix]) + + # subject + def createSmashStudySubjects(self, n=30): + ''' + Returns a list of tuples (subject, study_subject) + ''' + study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] + return [self.createSmashStudySubject(study=study) for _ in xrange(n)] + + def createSmashStudySubject(self, nd_number=None, subject=None, study=None, postponed=None, + datetime_contact_reminder=None, type=None, default_location=None, flying_team=None, + screening_number=None): # complete code and args... + + if nd_number is None: + nd_number = 'ND{:04}'.format(self.nd_number_ctr) + self.nd_number_ctr += 1 + + if subject is None: + subject = self.createSmashSubject() + + if study is None: + study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] + + if type is None: + type = choice([SUBJECT_TYPE_CHOICES_CONTROL, + SUBJECT_TYPE_CHOICES_PATIENT], 1, p=[0.2, 0.8]) + type = type[0] + + if default_location is None: + default_location = self.getAlreadyCreatedSmashLocation() + + if screening_number is None: + screening_number = self.createSmashScreeningNumber( + default_location.prefix) + + study_subject, _ = StudySubject.objects.update_or_create(nd_number=nd_number, subject=subject, + defaults={'default_location': default_location, 'type': type, + 'screening_number': screening_number, 'study': study}) + + self.alreadyCreatedStudySubjects.append(study_subject) + + return study_subject + + def createSmashSubject(self, first_name=None, last_name=None, languages=None, + default_written_communication_language=None, phone_number=None, social_security_number=None, + sex=None, country_id=None, date_born=None, address=None, + postal_code=None, city=None, country=None, dead=None): + + if first_name is None: + first_name = self.fake.first_name() + + if last_name is None: + last_name = self.fake.last_name() + + if languages is None: + languages = self.getAlreadyCreatedSmashLanguages() + + if default_written_communication_language is None: + getAlreadyCreatedSmashLanguages = languages[0] + + if phone_number is None: + phone_number = self.fake.phone_number() + + if social_security_number is None: + social_security_number = self.fake.ean(length=8) + + if sex is None: + sex = self.fake.word( + ext_word_list=[SEX_CHOICES_MALE, SEX_CHOICES_FEMALE]) + + if country_id is None: + country_id = COUNTRY_AFGHANISTAN_ID + + if date_born is None: + date_born = self.fake.date_of_birth( + minimum_age=50, maximum_age=105).strftime('%Y-%m-%d') + if address is None: + address = self.fake.address() + if postal_code is None: + postal_code = self.fake.postcode() + if city is None: + city = self.fake.word(ext_word_list=self.cities) + if country is None: + country = self.getAlreadyCreatedSmashCountry() + if dead is None: + dead = self.fake.boolean(chance_of_getting_true=1) + + dicc = {'first_name': first_name, 'last_name': last_name, + 'default_written_communication_language': default_written_communication_language, + 'phone_number': phone_number, 'social_security_number': social_security_number, + 'sex': sex, 'country_id': country_id, 'date_born': date_born, 'address': address, + 'postal_code': postal_code, 'city': city, 'country': country, 'dead': dead} + + subject, _ = Subject.objects.update_or_create(**dicc) + subject.languages.set(languages) + subject.save() + + self.alreadyCreatedSubjects.append(subject) + + return subject + + # user + def createSmashUser(self, username=None, email=None, password='smashtest007'): + ''' + username: string: Sets the username. By default None. Random values are generated unless other value is provided. + email: string: Sets the email. By default None. Random values are generated unless other value is provided. + password: string: Sets the password. By default password1234 unless other value is provided. + Returns a user + ''' + first_name = self.fake.first_name().lower() + last_name = self.fake.last_name().lower() + if username is None: + username = u'{}_{}'.format(first_name, last_name) + username = unidecode.unidecode(username).lower().replace(' ', '_') + + if email is None: + email = u'{}.{}@smash.lu'.format(first_name, last_name) + email = unidecode.unidecode(email) + # create user + defaults = {'email': email, 'password': password} + user, _ = User.objects.update_or_create( + username=username, defaults=defaults) + user.set_password(password) + user.save() + return user + + # worker + def createSmashWorkers(self, n=10): + ''' + :n int: number of workers to create + + Returns a list of tuples (user, worker, workerStuyRole) + ''' + logger.info('creating Smash Worker...') + return [self.createSmashWorker() for _ in xrange(n)] + + def createSmashWorker(self, first_name=None, last_name=None, username=None, email=None, + specialization=None, unit=None, phone_number=None, password='password1234', role=None, + locations=None, languages=None): + ''' + first_name: string: Sets the first_name. By default None. Random values are generated. Provide a value to overwrite random generation + last_name: string: Sets the last_name. By default None. Random values are generated. Provide a value to overwrite random generation + username: string: Sets the username. By default None. Random values are generated. Provide a value to overwrite random generation + email: string: Sets the email. By default None. Random values are generated. Provide a value to overwrite random generation + specialization: string: Sets the specialization. By default None. Random values are generated. Provide a value to overwrite random generation + unit: string: Sets the unit. By default None. Random values are generated. Provide a value to overwrite random generation + phone_number: string: Sets the phone_number. By default None. Random values are generated. Provide a value to overwrite random generation + password: string: Sets the password. By default None. Random values are generated. Provide a value to overwrite random generation + role: string: Sets the role. By default None. Random values are generated. Provide a value to overwrite random generation + locations: list: Sets the worker locations. By default None. Random values are generated. Provide a value to overwrite random generation + languages: list: Sets the worker languages. By default None. Random values are generated. Provide a value to overwrite random generation + Returns a tuple with (user, worker, workerStuyRole) + ''' + if first_name is None: + first_name = self.fake.first_name() + + if last_name is None: + last_name = self.fake.last_name() + + if username is None: + username = u'{}_{}'.format( + first_name, last_name).lower().replace(' ', '_') + + if email is None: + email = u'{}.{}@smash.lu'.format(first_name, last_name) + + if specialization is None: + specialization = self.getSmashSpecialization() + + if unit is None: + unit = self.getSmashUnit() + + if phone_number is None: + phone_number = self.fake.phone_number() + + if role is None: + role = self.getWorkerRole() + + # create user + user = self.createSmashUser( + username=username, email=email, password=password) + + # create worker + defaults = {'first_name': first_name, 'last_name': last_name, + 'email': email, 'unit': unit, 'specialization': specialization, + 'phone_number': phone_number, 'user': user} + worker, _ = Worker.objects.update_or_create(first_name=first_name, + last_name=last_name, defaults=defaults) + + if locations is None: + locations = [self.getAlreadyCreatedSmashLocation()] + worker.locations.set(locations) + if languages is None: + languages = self.getAlreadyCreatedSmashLanguages() + worker.languages.set(languages) + worker.save() + + self.alreadyCreatedWorkers.append(worker) + + # create workerStudyRole + workerStudyRole, _ = WorkerStudyRole.objects.update_or_create(worker=worker, + study_id=GLOBAL_STUDY_ID, role=role) + + # unit + def getSmashUnit(self): + return self.fake.word(ext_word_list=self.units) + + # specialization + def getSmashSpecialization(self): + return self.fake.word(ext_word_list=self.specialists) + + # languages + def createSmashLanguages(self): + ''' + Returns a list of Language objects + ''' + logger.info('creating Smash Languages...') + return [self.createSmashLanguage(name=name) for name in self.languages] + + def createSmashLanguage(self, name=None, locale=None): + ''' + Returns a Language object + ''' + if name is None: + name = self.fake.word(ext_word_list=self.languages) + + if locale is None: + if name in self.language_locale: + locale = self.language_locale[name] + else: + locale = DEFAULT_LOCALE_NAME + + language, _ = Language.objects.update_or_create( + name=name, locale=locale) + self.alreadyCreatedLanguages[name] = language + + if name in self.language_flags: + src = os.path.join(self.language_base_dir, + self.language_flags[name]) + basename = os.path.basename(src) + dst = os.path.join(MEDIA_ROOT, basename) + copyfile(src, dst) + # .save(basename, File(open(dst, 'rb'))) #SimpleUploadedFile(name=path, content=open(path, 'rb').read(), content_type='image/png') + language.image = basename + language.save() + + return language + + def getAlreadyCreatedSmashLanguages(self, max_n=3): + ''' + Returns a list of Languages from the already created languages, + if no languages are created, then the list is generated. + + max_n: int: maximum number of languages to return + + Returns a list of Language objects. + ''' + if len(self.alreadyCreatedLanguages.keys()) == 0: + self.createSmashLanguages() + + languages = choice(self.languages, max_n, p=self.languages_prob) + keys = set(languages) # unique + return [self.alreadyCreatedLanguages[key] for key in keys] + + # locations + def getAlreadyCreatedSmashLocation(self): + return self.random_element(self.alreadyCreatedLocations.values()) + + def createSmashLocations(self, n=5): + logger.info('creating Smash Locations...') + return [self.createSmashLocation() for _ in xrange(n)] + + def createSmashLocation(self, city=None, color=None, prefix=None): + if city is None: + city = choice(self.cities, 1, p=self.city_prob)[0] + + if color is None: + color = self.fake.hex_color() + + if prefix is None: + prefix = city[:1] + + location, _ = Location.objects.update_or_create( + name=city, color=color, prefix=prefix) + + self.alreadyCreatedLocations[city] = location + + return location + + def getAllCreatedLocations(self): + return self.alreadyCreatedLocations.values() + + def getAllCreatedLanguages(self): + return self.alreadyCreatedLanguages.values() + +if __name__ == "__main__": + if not os.path.exists(MEDIA_ROOT): + os.makedirs(MEDIA_ROOT) + fake = Faker() + fake.seed(4321) + fake.add_provider(smashProvider) + fake.createSmashFlyingTeams() + fake.createSmashItems() + fake.createSmashAppointmentTypes() + fake.createSmashRooms() + fake.createSmashLocations() + fake.createSmashLanguages() + fake.createSmashMailTemplates() + fake.createSmashCountries() + + fake.createSmashWorkers() + fake.createSmashAvailabilities() + fake.createSmashStudySubjects() + fake.createSmashAppointments() + fake.createSmashWorker(first_name=u'System', last_name=u'Admin', + email=u'carlos.vega@uni.lu', role=ROLE_CHOICES_TECHNICIAN, password='smashtest007', + locations=fake.getAllCreatedLocations(), languages=fake.getAllCreatedLanguages()) diff --git a/smash/web/forms/study_forms.py b/smash/web/forms/study_forms.py index 76c51a4288da0395d45e580bfa75ad5f689696e1..916f2316a4ff0c3f41a9f75840ea383a4d668bdb 100644 --- a/smash/web/forms/study_forms.py +++ b/smash/web/forms/study_forms.py @@ -1,8 +1,7 @@ import logging -from django.forms import ModelForm - -from web.models import Study, StudyNotificationParameters, StudyColumns +from django.forms import ModelForm, ValidationError +from web.models import Study, StudyNotificationParameters, StudyColumns, StudySubject logger = logging.getLogger(__name__) @@ -12,6 +11,16 @@ class StudyEditForm(ModelForm): def __init__(self, *args, **kwargs): super(StudyEditForm, self).__init__(*args, **kwargs) + def clean_nd_number_study_subject_regex(self): + nd_number_study_subject_regex = self.cleaned_data.get( + 'nd_number_study_subject_regex') + + if StudySubject.check_nd_number_regex(nd_number_study_subject_regex) == False: + raise ValidationError( + 'Please enter a valid nd_number_study_subject_regex regex.') + + return nd_number_study_subject_regex + class Meta: model = Study fields = '__all__' @@ -21,7 +30,8 @@ class StudyEditForm(ModelForm): class StudyNotificationParametersEditForm(ModelForm): def __init__(self, *args, **kwargs): - super(StudyNotificationParametersEditForm, self).__init__(*args, **kwargs) + super(StudyNotificationParametersEditForm, + self).__init__(*args, **kwargs) class Meta: model = StudyNotificationParameters diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py index 44afb25786f47a3d43e6a7f56a9ea1387ccbdab6..76d4d7735119ae75280a3e7a42550d051879c632 100644 --- a/smash/web/forms/study_subject_forms.py +++ b/smash/web/forms/study_subject_forms.py @@ -197,7 +197,7 @@ def validate_subject_nd_number(self, cleaned_data): if self.study.columns.nd_number: nd_number = cleaned_data['nd_number'] if nd_number != "": - if re.match('ND[0-9][0-9][0-9][0-9]', nd_number) is None: + if not self.study.check_nd_number(nd_number): self.add_error('nd_number', "Invalid ND number") else: subjects_from_db = StudySubject.objects.filter(nd_number=nd_number, study=self.study) diff --git a/smash/web/migrations/0119_auto_20181024_1158.py b/smash/web/migrations/0119_auto_20181024_1158.py new file mode 100644 index 0000000000000000000000000000000000000000..cab12fa337487854b82a8cf770efd1cda340dd36 --- /dev/null +++ b/smash/web/migrations/0119_auto_20181024_1158.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2018-10-24 11:58 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0118_voucher_activity_type'), + ] + + operations = [ + migrations.AlterField( + model_name='studyvisitlist', + name='type', + field=models.CharField(choices=[(b'UNFINISHED', b'Unfinished visits'), (b'APPROACHING_WITHOUT_APPOINTMENTS', b'Approaching visits'), (b'APPROACHING_FOR_MAIL_CONTACT', b'Post mail for approaching visits'), (b'GENERIC', b'Generic visit list'), (b'MISSING_APPOINTMENTS', b'Visits with missing appointments'), (b'EXCEEDED_TIME', b'Exceeded visit time')], max_length=50, verbose_name=b'Type of list'), + ), + migrations.AlterField( + model_name='workerstudyrole', + name='role', + field=models.CharField(choices=[(b'DOCTOR', b'Doctor'), (b'NURSE', b'Nurse'), (b'PSYCHOLOGIST', b'Psychologist'), (b'TECHNICIAN', b'Technician'), (b'SECRETARY', b'Secretary'), (b'PROJECT MANAGER', b'Project Manager')], max_length=20, verbose_name=b'Role'), + ), + ] diff --git a/smash/web/migrations/0120_study_nd_number_study_subject_regex.py b/smash/web/migrations/0120_study_nd_number_study_subject_regex.py new file mode 100644 index 0000000000000000000000000000000000000000..9b4146edb8a1bcdfbf19a3144c4e9b932ee0beb6 --- /dev/null +++ b/smash/web/migrations/0120_study_nd_number_study_subject_regex.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2018-10-24 12:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0119_auto_20181024_1158'), + ] + + operations = [ + migrations.AddField( + model_name='study', + name='nd_number_study_subject_regex', + field=models.CharField(default=b'^ND\\d{4}$', help_text=b'Defines the regex to check the identification number used for each study subject.', max_length=255, verbose_name=b'Study Subject ND Number Regex'), + ), + ] diff --git a/smash/web/migrations/0121_auto_20181024_1859.py b/smash/web/migrations/0121_auto_20181024_1859.py new file mode 100644 index 0000000000000000000000000000000000000000..2e0b4654936fdae97a6ddc06809d3a89918a17d9 --- /dev/null +++ b/smash/web/migrations/0121_auto_20181024_1859.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2018-10-24 18:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0120_study_nd_number_study_subject_regex'), + ] + + operations = [ + migrations.AlterField( + model_name='study', + name='nd_number_study_subject_regex', + field=models.CharField(default=b'^ND\\d{4}$', help_text=b'Defines the regex to check the ID used for each study subject. Keep in mind that this regex should be valid for all previous study subjects in the database.', max_length=255, verbose_name=b'Study Subject ND Number Regex'), + ), + ] diff --git a/smash/web/migrations/0125_merge_20181025_0745.py b/smash/web/migrations/0125_merge_20181025_0745.py new file mode 100644 index 0000000000000000000000000000000000000000..9559a6193bdeeb50cef4d5ee8d27af03c56dbac1 --- /dev/null +++ b/smash/web/migrations/0125_merge_20181025_0745.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2018-10-25 07:45 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0124_auto_20181017_1532'), + ('web', '0121_auto_20181024_1859'), + ] + + operations = [ + ] diff --git a/smash/web/models/study.py b/smash/web/models/study.py index bfff2a6e067dd09fdb9803f4a261ec57c5412547..c7b90db332762b349b86edca75fe179f6cc620e7 100644 --- a/smash/web/models/study.py +++ b/smash/web/models/study.py @@ -3,13 +3,20 @@ from django.db import models from web.models import StudyColumns, StudyNotificationParameters +import re + class Study(models.Model): + class Meta: app_label = 'web' name = models.CharField(max_length=255, verbose_name='Name') + nd_number_study_subject_regex = models.CharField( + max_length=255, verbose_name='Study Subject ND Number Regex', default=r'^ND\d{4}$', + help_text='Defines the regex to check the ID used for each study subject. Keep in mind that this regex should be valid for all previous study subjects in the database.') + columns = models.OneToOneField( StudyColumns, on_delete=models.CASCADE, @@ -24,6 +31,10 @@ class Study(models.Model): verbose_name="Auto create follow up visit" ) + def check_nd_number(self, nd_number): + regex = re.compile(self.nd_number_study_subject_regex) + return regex.match(nd_number) is not None + def __str__(self): return "%s" % self.name @@ -44,4 +55,4 @@ class Study(models.Model): if len(study) > 0: return study[0] else: - return None \ No newline at end of file + return None diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py index 3854138909c993a2dbd98168dffd849b6fa48d97..e62425531401fd2e4808e49797649dbb87625ee4 100644 --- a/smash/web/models/study_subject.py +++ b/smash/web/models/study_subject.py @@ -1,5 +1,6 @@ # coding=utf-8 import logging +import re from django.db import models from web.models import VoucherType, Appointment, Location, Visit @@ -7,7 +8,9 @@ from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES, FILE_STORAG logger = logging.getLogger(__name__) + class StudySubject(models.Model): + class Meta: app_label = 'web' @@ -18,7 +21,8 @@ class StudySubject(models.Model): visit.save() def finish_all_appointments(self): - appointments = Appointment.objects.filter(visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED) + appointments = Appointment.objects.filter( + visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED) for appointment in appointments: appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED appointment.save() @@ -164,18 +168,28 @@ class StudySubject(models.Model): for part in parts: chunks = part.strip().split('-') if len(chunks) == 2: - letter, number = chunks - tupl = (letter, int(number)) + letter, number = chunks + tupl = (letter, int(number)) else: - logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format(len(chunks), self.screening_number)) - tupl = (part.strip(), None) + logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format( + len(chunks), self.screening_number)) + tupl = (part.strip(), None) if pattern is not None and pattern in part: matches.append(tupl) else: reminder.append(tupl) return matches + sorted(reminder, reverse=reverse) - + + @staticmethod + def check_nd_number_regex(regex_str): + nd_numbers = StudySubject.objects.all().values_list('nd_number', flat=True) + regex = re.compile(regex_str) + for nd_number in nd_numbers: + if regex.match(nd_number) is None: + return False + return True + def __str__(self): return "%s %s" % (self.subject.first_name, self.subject.last_name) diff --git a/smash/web/templates/study/edit.html b/smash/web/templates/study/edit.html index 54be5ce7bf8131024a775eea19c4ca053ee51e5c..e1e8d594d914cae156ec0bbac6bb0b134b9e488b 100644 --- a/smash/web/templates/study/edit.html +++ b/smash/web/templates/study/edit.html @@ -6,7 +6,12 @@ {{ block.super }} <!-- DataTables --> <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}"> - + <style type="text/css"> + .tooltip-inner { + max-width: 350px; + width: 350px; + } + </style> {% include "includes/datepicker.css.html" %} {% include "includes/datetimepicker.css.html" %} {% endblock styles %} @@ -45,6 +50,9 @@ <div class="col-md-6 form-group {% if field.errors %}has-error{% endif %}"> <label for="{# TODO #}" class="col-sm-4 control-label"> {{ field.label }} + {% if field.help_text %} + <i class="fa fa-info-circle" aria-hidden="true" data-toggle="tooltip" data-placement="bottom" title="{{field.help_text}}"></i> + {% endif %} </label> <div class="col-sm-8"> diff --git a/smash/web/tests/forms/test_study_forms.py b/smash/web/tests/forms/test_study_forms.py new file mode 100644 index 0000000000000000000000000000000000000000..a150a83c181cee62dcf894648ed7d5bb797a0279 --- /dev/null +++ b/smash/web/tests/forms/test_study_forms.py @@ -0,0 +1,40 @@ +from django.test import TestCase +from django.forms import ValidationError +from web.tests.functions import create_study_subject +from web.forms.study_forms import StudyEditForm +from web.models.study import Study +from web.models.study_subject import StudySubject + +class StudyFormTests(TestCase): + + def test_study_default_regex(self): + # this will add a studysubject with a NDnumber + StudySubject.objects.all().delete() + create_study_subject(nd_number='ND0001') + form = StudyEditForm() + # set default regex + nd_number_study_subject_regex_default = Study._meta.get_field( + 'nd_number_study_subject_regex').get_default() + form.cleaned_data = { + 'nd_number_study_subject_regex': nd_number_study_subject_regex_default} + self.assertTrue(form.clean_nd_number_study_subject_regex() + == nd_number_study_subject_regex_default) + # test wrong regex + form = StudyEditForm() + nd_number_study_subject_regex_default = r'^nd\d{5}$' + form.cleaned_data = { + 'nd_number_study_subject_regex': nd_number_study_subject_regex_default} + self.assertRaises( + ValidationError, form.clean_nd_number_study_subject_regex) + + def test_study_other_regex(self): + StudySubject.objects.all().delete() + # this will add a studysubject with a NDnumber + create_study_subject(nd_number='nd00001') + form = StudyEditForm() + # test new regex + nd_number_study_subject_regex_default = r'^nd\d{5}$' + form.cleaned_data = { + 'nd_number_study_subject_regex': nd_number_study_subject_regex_default} + self.assertTrue(form.clean_nd_number_study_subject_regex() + == nd_number_study_subject_regex_default) diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py index ed17deb7e769ffdb6125cb317302840c934b29e6..617756cbd2fa6b5530da8a9afcb0b6dd82b8c6b2 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -186,16 +186,22 @@ def create_subject(): ) -def create_study_subject(subject_id=1, subject=None): +def create_study_subject(subject_id=1, subject=None, nd_number='ND0001'): if subject is None: subject = create_subject() - return StudySubject.objects.create( + study_subject = StudySubject.objects.create( default_location=get_test_location(), type=SUBJECT_TYPE_CHOICES_CONTROL, screening_number="piotr's number" + str(subject_id), study=get_test_study(), subject=subject ) + if nd_number is not None: + study_subject.nd_number = nd_number + study_subject.save() + + return study_subject + def create_study_subject_with_multiple_screening_numbers(subject_id=1, subject=None): if subject is None: @@ -242,7 +248,8 @@ def create_worker(user=None, with_test_location=False): if with_test_location: worker.locations = [get_test_location()] worker.save() - WorkerStudyRole.objects.create(worker=worker, study_id=GLOBAL_STUDY_ID, role=ROLE_CHOICES_DOCTOR) + WorkerStudyRole.objects.create( + worker=worker, study_id=GLOBAL_STUDY_ID, role=ROLE_CHOICES_DOCTOR) return worker @@ -255,7 +262,8 @@ def create_voucher_partner(): unit="LCSB", phone_number="0123456789" ) - WorkerStudyRole.objects.create(worker=worker, study_id=GLOBAL_STUDY_ID, role=WORKER_VOUCHER_PARTNER) + WorkerStudyRole.objects.create( + worker=worker, study_id=GLOBAL_STUDY_ID, role=WORKER_VOUCHER_PARTNER) return worker @@ -358,10 +366,12 @@ def format_form_field(value): def prepare_test_redcap_connection(): Language.objects.create(name="Finnish").save() Language.objects.create(name="Italian").save() - token_item = ConfigurationItem.objects.filter(type=REDCAP_TOKEN_CONFIGURATION_TYPE)[0] + token_item = ConfigurationItem.objects.filter( + type=REDCAP_TOKEN_CONFIGURATION_TYPE)[0] # noinspection SpellCheckingInspection token_item.value = "5C75EEC3DBDDA5218B6ACC0424B3F695" token_item.save() - url_item = ConfigurationItem.objects.filter(type=REDCAP_BASE_URL_CONFIGURATION_TYPE)[0] + url_item = ConfigurationItem.objects.filter( + type=REDCAP_BASE_URL_CONFIGURATION_TYPE)[0] url_item.value = "https://luxparktest.org/redcap/" url_item.save() diff --git a/smash/web/tests/models/test_study.py b/smash/web/tests/models/test_study.py index 9c9e02f77c08015bd59a4ef19149945caa5a0e6b..c535aa57a01becec96c39fb51ef4da443105de5a 100644 --- a/smash/web/tests/models/test_study.py +++ b/smash/web/tests/models/test_study.py @@ -1,14 +1,22 @@ import logging from django.test import TestCase - -from web.tests.functions import create_study +from django.forms import ValidationError +from web.tests.functions import create_study, create_study_subject +from web.forms.study_forms import StudyEditForm +from web.models.study import Study +from web.models.study_subject import StudySubject logger = logging.getLogger(__name__) class StudyTests(TestCase): + def test_image_img(self): study = create_study() - self.assertTrue(study.name in str(study)) + + def test_check_nd_number(self): + study = create_study() + # check the default regex + self.assertTrue(study.check_nd_number('ND0001')) diff --git a/smash/web/tests/models/test_study_subject.py b/smash/web/tests/models/test_study_subject.py index b70add8cee109783433fdba1e5056bf60b1235cd..f91762f35e0df2c664e7b69b00f0cb40dd45192d 100644 --- a/smash/web/tests/models/test_study_subject.py +++ b/smash/web/tests/models/test_study_subject.py @@ -2,41 +2,73 @@ from django.test import TestCase from web.models import Appointment from web.models import Visit +from web.models import StudySubject, Study from web.tests.functions import create_study_subject, create_appointment, create_study_subject_with_multiple_screening_numbers from web.tests.functions import create_visit class SubjectModelTests(TestCase): + def test_mark_as_resigned(self): subject = create_study_subject() visit = create_visit(subject) appointment = create_appointment(visit) subject.mark_as_resigned() - appointment_status = Appointment.objects.filter(id=appointment.id)[0].status + appointment_status = Appointment.objects.filter(id=appointment.id)[ + 0].status visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished self.assertTrue(subject.resigned) self.assertTrue(visit_finished) - self.assertEquals(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEquals( + Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + + def test_check_nd_number_regex(self): + # delete everything + StudySubject.objects.all().delete() + # get default regex + nd_number_study_subject_regex_default = Study._meta.get_field( + 'nd_number_study_subject_regex').get_default() + self.assertTrue(StudySubject.check_nd_number_regex( + nd_number_study_subject_regex_default)) + # this will add a studysubject with a NDnumber + study_subject = create_study_subject(nd_number='ND0001') + + self.assertTrue(StudySubject.check_nd_number_regex( + nd_number_study_subject_regex_default)) + # delete everything + StudySubject.objects.all().delete() + # this will add a studysubject with a NDnumber + create_study_subject(nd_number='ND00001') + self.assertFalse(StudySubject.check_nd_number_regex( + nd_number_study_subject_regex_default)) def test_sort_matched_screening_first(self): def create_result(phrase, subject_id=1): phrase = phrase.format(subject_id=subject_id) phrase = phrase.split(';') - for i,r in enumerate(phrase): + for i, r in enumerate(phrase): letter, num = phrase[i].strip().split('-') phrase[i] = (letter, int(num)) return phrase - subject = create_study_subject_with_multiple_screening_numbers(subject_id=1) - self.assertEqual(subject.sort_matched_screening_first('L'), create_result('L-00{subject_id}; E-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('L-00'), create_result('L-00{subject_id}; E-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('E'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('-'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first(''), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('001'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('00'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('potato'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) - \ No newline at end of file + subject = create_study_subject_with_multiple_screening_numbers( + subject_id=1) + self.assertEqual(subject.sort_matched_screening_first('L'), create_result( + 'L-00{subject_id}; E-00{subject_id}', subject_id=1)) + self.assertEqual(subject.sort_matched_screening_first( + 'L-00'), create_result('L-00{subject_id}; E-00{subject_id}', subject_id=1)) + self.assertEqual(subject.sort_matched_screening_first('E'), create_result( + 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) + self.assertEqual(subject.sort_matched_screening_first( + '-'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) + self.assertEqual(subject.sort_matched_screening_first(''), create_result( + 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) + self.assertEqual(subject.sort_matched_screening_first('001'), create_result( + 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) + self.assertEqual(subject.sort_matched_screening_first('00'), create_result( + 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) + self.assertEqual(subject.sort_matched_screening_first('potato'), create_result( + 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) diff --git a/smash/web/tests/view/test_appointments.py b/smash/web/tests/view/test_appointments.py index e17bb743efcab2bf5f7b467e2786592f7ed1c566..4acf168d587ae17c78e9748b54e56fb6e6220d10 100644 --- a/smash/web/tests/view/test_appointments.py +++ b/smash/web/tests/view/test_appointments.py @@ -137,7 +137,7 @@ class AppointmentsViewTests(LoggedInTestCase): self.assertEqual(Appointment.APPOINTMENT_STATUS_FINISHED, appointment_result.status) def test_save_appointments_edit_with_invalid_nd_number(self): - subject = create_study_subject() + subject = create_study_subject(nd_number=None) visit = create_visit(subject) appointment = create_appointment(visit, get_today_midnight_date()) diff --git a/smash/web/tests/view/test_worker.py b/smash/web/tests/view/test_worker.py index 9c49f8d7a80bd1a7053fb468220e92204e43cc5c..bead17b6e0286145621d9418bdc8461242ea974b 100644 --- a/smash/web/tests/view/test_worker.py +++ b/smash/web/tests/view/test_worker.py @@ -5,7 +5,7 @@ from django_common.auth_backends import User from web.forms import WorkerForm from web.models import Worker -from web.models.worker_study_role import WORKER_STAFF, ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER +from web.models.worker_study_role import WORKER_STAFF, ROLE_CHOICES_DOCTOR, WORKER_HEALTH_PARTNER, WORKER_VOUCHER_PARTNER from web.tests import create_worker from web.tests.functions import create_language, create_location, create_availability, format_form_field from .. import LoggedInTestCase @@ -16,10 +16,18 @@ logger = logging.getLogger(__name__) class WorkerViewTests(LoggedInTestCase): def test_render_workers_list_request(self): create_worker() - response = self.client.get(reverse('web.views.workers')) self.assertEqual(response.status_code, 200) + def test_render_worker_type_request(self): + create_worker() + response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_STAFF})) + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_HEALTH_PARTNER})) + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_VOUCHER_PARTNER})) + self.assertEqual(response.status_code, 200) + def test_render_add_worker_request(self): response = self.client.get(reverse('web.views.worker_add', kwargs={'worker_type': WORKER_STAFF})) self.assertEqual(response.status_code, 200) diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py index bc782e45a686222e32dab5465f3cbff5f158cd2b..ee45e0d173bc980f05d866df56cb70387ae05830 100644 --- a/smash/web/views/appointment.py +++ b/smash/web/views/appointment.py @@ -6,12 +6,13 @@ from django.contrib import messages from django.core.exceptions import ValidationError from django.shortcuts import get_object_or_404, redirect +from web.models.constants import GLOBAL_STUDY_ID from web.models.appointment_list import APPOINTMENT_LIST_APPROACHING, APPOINTMENT_LIST_GENERIC, \ APPOINTMENT_LIST_UNFINISHED from . import wrap_response from web.forms import AppointmentDetailForm, AppointmentEditForm, AppointmentAddForm, SubjectEditForm, \ StudySubjectEditForm -from ..models import Appointment, StudySubject, MailTemplate, Visit +from ..models import Appointment, StudySubject, MailTemplate, Visit, Study logger = logging.getLogger(__name__) @@ -73,6 +74,7 @@ def appointment_edit(request, id): study_subject_form = None subject_form = None contact_attempts = None + study = Study.get_by_id(GLOBAL_STUDY_ID) if request.method == 'POST': appointment_form = AppointmentEditForm(request.POST, @@ -101,7 +103,7 @@ def appointment_edit(request, id): status=Appointment.APPOINTMENT_STATUS_FINISHED).count() == 0: adjust_date = True if appointment_form.cleaned_data["status"] == Appointment.APPOINTMENT_STATUS_FINISHED: - if re.match('ND[0-9][0-9][0-9][0-9]', study_subject_form.cleaned_data["nd_number"]) is None: + if not study.check_nd_number(study_subject_form.cleaned_data["nd_number"]): study_subject_form.add_error('nd_number', ValidationError("invalid ND number")) is_valid_form = False if is_valid_form: