def create_thread_ndb(actor_id, recipients, text, subject=''): """Creates a new thread with given recipients, body text, and subject. TODO(kanat): check if recipient is a valid account. :param actor_id: (int) ID of the account acting. :param recipients: (list) list of account IDs in the thread. :param text: (str) body text of the initial message. :param subject: (str) optional subject line of the thread. :return: (kinds.messages.Thread) """ asserts.valid_id_type(actor_id) asserts.not_empty(recipients) asserts.type_of(text, basestring) asserts.type_of(subject, basestring) thd = Thread() for account_id in set(recipients): if account_id != actor_id: thd.add_member(account_id) if not len(thd.members): raise exp.BadRequestExp('Recipients not specified.') thd.add_member(actor_id) thd.put() # Populate MessageDto. message_dto = MessageDto() message_dto.thread_id = thd.id message_dto.text = text send_ndb(actor_id, message_dto) return thd
def select_applicant(actor_id, job_id, caregiver_id): """Select an applicant for the job. TODO(kanat): Notify selected applicant. :param actor_id: (int) ID of the account acting. :param caregiver_id: (int) ID of the job applicant's account. :return: (None) """ asserts.valid_id_type(caregiver_id) _assert_job_owner(actor_id, job_id) job = get_by_id_ndb(job_id) if job.status != JobStatus.Open: raise exp.BadRequestExp('Job is not open') if actor_id == caregiver_id: raise exp.BadRequestExp('Can\'t select yourself.') apps = _get_applicants(job_id) applicant = apps.find_applicant(caregiver_id) if applicant is None: raise exp.NotFoundExp('Applicant not found.') # Set up job invoice. invoice = JobInvoice(job_id=job.id, start_date=job.start_date, end_date=job.end_date) invoice.put() apps.selected = applicant job.status = JobStatus.Accepted job.invoice_id = invoice.id ndb.put_multi([apps, job])
def send_request(actor_id, to_id): """Sends a request from one account to another. TODO(kanat): Send email notification. :param actor_id: (int) ID of the account sending. :param to_id: (int) ID of the account receiving. :return: (kinds.connections.ConnRequest) Connection request that was created. """ asserts.valid_id_type(actor_id) asserts.valid_id_type(to_id) if actor_id == to_id: raise exp.BadRequestExp('Cannot send a request to self.') if not services.accounts.account_by_id(to_id): logging.warning('to_id={} does not exist.'.format(to_id)) raise exp.NotFoundExp('Account does not exist.') # Maybe one or the other already sent a request. req = (_conn_request_get(from_id=actor_id, to_id=to_id) or _conn_request_get(from_id=to_id, to_id=actor_id)) if req is not None: logging.info('Connection request already exists.') return req = ConnRequest(from_id=actor_id, to_id=to_id) req.put() return req
def remove_connection(actor_id, other_id): """Removes a connection from the account. :param actor_id: (int) ID of the account removing the connection. :param other_id: ID of the account to be removed. :return: (None) """ asserts.valid_id_type(actor_id) asserts.valid_id_type(other_id) if not is_connected(actor_id, other_id): logging.warning('Trying to remove a connection that doesn\'t exist: ' 'actor={}, other={}'.format(actor_id, other_id)) raise exp.NotFoundExp('Not connected.') connlistA = _get_connlist(actor_id) connlistB = _get_connlist(other_id) # Find the connection request. req = (_conn_request_get(from_id=actor_id, to_id=other_id) or _conn_request_get(from_id=other_id, to_id=actor_id)) try: connlistA.accepted_reqs.remove(req.id) connlistB.accepted_reqs.remove(req.id) except ValueError: pass ndb.put_multi([connlistA, connlistB]) req.key.delete()
def posted_jobs_ndb(account_id): """Returns jobs posted by the given account_id. :param account_id: (int) ID of the account. :return: (list<kinds.jobs.Job>) """ asserts.valid_id_type(account_id) jobs = Job.gql('WHERE account_id = :1', account_id).fetch() return jobs
def applied_jobs_ndb(account_id): """Returns jobs for which the given account_id applied. :param account_id: (int) ID of the account. :return: (list<kinds.jobs.Job>) """ asserts.valid_id_type(account_id) apps = JobApps.gql('WHERE applicants.account_id = :1', account_id) return _jobs_from_apps(apps)
def sent_requests(actor_id): """Retrieves sent requests. :param actor_id: (int) ID of the account. :return: (list<kinds.connections.ConnRequest>) A list of connection requests. """ asserts.valid_id_type(actor_id) reqs = _conn_requests(from_id=actor_id, status=ConnStatus.Pending) return reqs
def is_connected(first_id, second_id): """Check whether two accounts are connected. :param first_id: (int) ID of the first account. :param second_id: (int) ID of the second account. :return: (bool) True if connected; False otherwise. """ asserts.valid_id_type(first_id) asserts.valid_id_type(second_id) connlist = _get_connlist(first_id) return second_id in connlist.accepted_ids
def pending_requests(actor_id): """Retrieves pending requests that were sent to an account. :param actor_id: (int) ID of the account. :return: (list<dto.connections.PendingDto>) A list of connection Dto's. """ asserts.valid_id_type(actor_id) reqs = _conn_requests(from_id=actor_id, status=ConnStatus.Pending) account_ids = [r.from_id for r in reqs] accounts = services.accounts.accounts_by_ids(account_ids) return PendingDto.list_from_req_account(reqs, accounts)
def nearby_jobs_ndb(account_id): """Returns jobs posted nearby the give account_id. NOTE: For now, just returns the list of all open jobs. :param account_id: (int) ID of the account. :return: (list<kinds.jobs.Job>) """ asserts.valid_id_type(account_id) jobs = Job.gql('WHERE status = :1 ORDER BY updated DESC', JobStatus.Open).fetch(limit=25) return jobs
def my_connections(actor_id): """Returns the list of account IDs the account is connected to. :param actor_id: (int) ID of the account. :return: (list<dto.connections.ConnDto>) A list of account IDs. """ asserts.valid_id_type(actor_id) connlist = _get_connlist(actor_id) reqs = ndb.get_multi(ConnRequest.ids_to_keys(connlist.accepted_reqs)) accounts = services.accounts.accounts_by_ids(connlist.accepted_ids) return ConnDto.list_from_req_account(reqs, accounts)
def get_by_id_ndb(job_id): """Returns the job associated with the given job_id. :param job_id: (int) ID of the job. :return: (kinds.jobs.Job) """ asserts.valid_id_type(job_id) job = Job.get_by_id(job_id) if job is None: logging.warning('Job not found. id={}'.format(job_id)) raise exp.NotFoundExp() return job
def threads_ndb(actor_id, limit=25): """Returns the latest threads the account is a member of. :param actor_id: (int) ID of the account. :param limit: (int) max number of threads. :return: (list<kinds.messages.Thread>) """ asserts.valid_id_type(actor_id) query = Thread.query( Thread.members == Member(account_id=actor_id, hidden=False)) query = query.order(-Thread.updated) thds = query.fetch(limit=limit) return thds
def patient_remove(actor_id, patient_id): """Removes the given patient_id from the given account. :param actor_id: (int) ID of the account performing the action. :param patient_id: (int) ID of the patient to be removed. :return: (None) """ asserts.valid_id_type(actor_id) asserts.valid_id_type(patient_id) account = account_by_id(actor_id, _dto=False) patient = patient_by_id(account.id, patient_id, _dto=False) if patient is not None and patient.id in account.patient_ids: account.patient_ids.remove(patient.id) patient.soft_delete = True ndb.put_multi([account, patient])
def patients_by_account(account_id, _dto=True): """Returns the list of patients that belong to the given account_id. :param account_id: (int) ID of the account. :return: (list<dto.accounts.PatientDto>) if _dto is True (list<kinds.accounts.Patient>) if _dto is False (None) if patients don't exist. """ asserts.valid_id_type(account_id) account = account_by_id(account_id, _dto=False) if account.patient_ids is not None: keys = Patient.ids_to_keys(account.patient_ids) patients = ndb.get_multi(keys) if not _dto: return patients return [PatientDto.from_patient_ndb(p) for p in patients]
def leave_thread(actor_id, thread_id): """Unsubscribes the account from the thread. TODO(kanat): Send a system message since the thread has been updated. :param actor_id: (int) ID of the account acting. :param thread_id: (int) ID of the thread to leave. :return: (None) """ asserts.valid_id_type(actor_id) asserts.valid_id_type(thread_id) thread = Thread.get_by_id(thread_id) member = thread.find_member(actor_id) if member is not None and member.hidden is False: member.hidden = True thread.put()
def caregiver_by_account(account_id, _dto=True): """Returns the caregiver details that belongs to the given account_id. :param account_id: (int) ID of the account. :return: (dto.accounts.CaregiverDto) if _dto is True (kinds.accounts.Caregiver) if _dto is False (None) if account or caregiver does not exist. """ asserts.valid_id_type(account_id) account = account_by_id(account_id, _dto=False) if not asserts.is_valid_id_type(account.caregiver_id): raise exp.BadRequestExp() caregiver = Caregiver.get_by_id(account.caregiver_id) if not _dto: return caregiver return CaregiverDto.from_caregiver_ndb(caregiver)
def hide_message(actor_id, message_id): """Hides the given message from the account. :param actor_id: (int) ID of the account acting. :param message_id: (int) ID of the message to hide. :return: (None) """ asserts.valid_id_type(actor_id) asserts.valid_id_type(message_id) msg = Message.get_by_id(message_id) _assert_thread_member(actor_id, msg.thread_id) if actor_id not in msg.hidden_member_ids: msg.hidden_member_ids.append(actor_id) msg.put()
def messages_ndb(actor_id, thread_id, limit=25): """Returns the latest messages in the given thread. TODO(kanat): Update kinds.Messages.Member.last_seen. :param actor_id: (int) ID of the account acting. :param thread_id: (int) ID of the thread. :param limit: (int) max number of messages. :return: (list<kinds.messages.Message>) """ asserts.valid_id_type(actor_id) asserts.valid_id_type(thread_id) _assert_thread_member(actor_id, thread_id) msgs = Message.gql('WHERE thread_id = :1 ORDER BY created DESC', thread_id).fetch(limit=limit) return [msg for msg in msgs if actor_id not in msg.hidden_member_ids]
def account_by_id(account_id, _dto=True, _throw=True): """Returns the account associated with the given account_id. :param account_id: (int) ID of the account. :return: (dto.accounts.AccountDto) if _dto is True. (kinds.accounts.Account) if _dto is False. (None) if account does not exist. :raise: (exp.NotFoundExp) if `_throw` is True and account is not found. """ asserts.valid_id_type(account_id) account = Account.get_by_id(account_id) if account is None and _throw: logging.warning('account not found. id={}'.format(account_id)) raise exp.NotFoundExp('Account not found.') if not _dto: return account return AccountDto.from_account_ndb(account)
def patient_by_id(actor_id, patient_id, _dto=True): """Returns the patient associated with the given account and patient_id. :param actor_id: (int) ID of the account performing the action. :param patient_id: (int) ID of the patient to retrieve. :return: (dto.accounts.PatientDto) if _dto is True (kinds.accounts.Patient) if _dto is False """ asserts.valid_id_type(actor_id) asserts.valid_id_type(patient_id) account = account_by_id(actor_id, _dto=False) if patient_id not in account.patient_ids: raise exp.NotFoundExp('Care recipient not found.') patient = Patient.get_by_id(patient_id) if not _dto: return patient return PatientDto.from_patient_ndb(patient)
def decline_request(actor_id, from_id): """Declines a connection request from the given account. :param actor_id: (int) ID of the account. :param from_id: (int) ID of the account that sent the connection request. :return: (None) """ asserts.valid_id_type(actor_id) asserts.valid_id_type(from_id) req = _conn_request_get(to_id=actor_id, from_id=from_id, status=ConnStatus.Pending) if req is None: logging.warning( 'Pending request does not exist: to={}, from={}'.format( actor_id, from_id)) raise exp.NotFoundExp('Request does not exist.') req.key.delete()
def _cancel_by_caregiver(actor_id, job_id, message=None): """Cancel a job the the selected caregiver. TODO(kanat): Notify careseeker. :return: (None) """ asserts.valid_id_type(actor_id) job = get_by_id_ndb(job_id) if job.status == JobStatus.Cancelled: logging.info('Job already cancelled.') return apps = _get_applicants(job.id) if apps.selected is None or apps.selected.account_id != actor_id: raise exp.PermissionExp() logging.info('Job cancelled by caregiver. job_id={}'.format(job_id)) apps.selected = None job.status = JobStatus.Cancelled ndb.put_multi([apps, job])
def accept_request(actor_id, from_id): """Accepts a connection request from the given account. TODO(kanat): Send email notification. :param actor_id: (int) ID of the account. :param from_id: (int) ID of the account that sent the connection request. :return: (kinds.connections.ConnRequest) Accepted connection request. """ asserts.valid_id_type(actor_id) asserts.valid_id_type(from_id) req = _conn_request_get(to_id=actor_id, from_id=from_id, status=ConnStatus.Pending) if req is None: logging.warning( 'Pending request does not exist: to={}, from={}'.format( actor_id, from_id)) raise exp.NotFoundExp('Request does not exist.') _accept_request(req) return req
def create_job_ndb(account_id, job_dto): """Create a new job. TODO(kanat): Proper input validation, e.g: - valid patient_id? - valid start, end dates? - valid wage? :param account_id: (int) ID of the account for which the job belongs. :param job_dto: (dto.jobs.JobDto) job details. :return: (kinds.jobs.Job) """ asserts.valid_id_type(account_id) asserts.type_of(job_dto, JobDto) job = JobDto.to_job_ndb(job_dto) job.account_id = account_id job.put() apps = JobApps(job_id=job.id) apps.put() job.apps_id = apps.id job.put() return job
def send_ndb(actor_id, message_dto): """Sends a reply in a thread. :param actor_id: (int) ID of the account acting. :param message_dto: (dto.messages.MessageDto) message details. :return: (kinds.messages.Message) """ asserts.valid_id_type(actor_id) asserts.type_of(message_dto, MessageDto) thread_id = message_dto.thread_id _assert_thread_member(actor_id, thread_id) thd = Thread.get_by_id(thread_id) msg = Message(thread_id=thd.id, sender_id=actor_id) msg.text = message_dto.text if hasattr(message_dto, 'message_type'): msg.message_type = message_dto.message_type msg.put() thd.last_message_id = msg.id thd.put() return msg
def apply_job(actor_id, job_id, message=None): """Apply for a job. TODO(kanat): Notify job owner. :param actor_id: (int) ID of the account acting. :param job_id: (int) ID of the job applying. :param message: (str) Optional message. :return: (None) """ asserts.valid_id_type(actor_id) asserts.valid_id_type(job_id) job = get_by_id_ndb(job_id) if job.status != JobStatus.Open: raise exp.BadRequestExp('Job is not open.') if actor_id == job.account_id: raise exp.BadRequestExp('Can\'t apply to own job.') apps = _get_applicants(job_id) if apps.find_applicant(actor_id) is not None: logging.warning('Already applied.') return apps.add_applicant(actor_id, message) apps.put()
def _is_job_owner(actor_id, job_id): asserts.valid_id_type(actor_id) job = get_by_id_ndb(job_id) return actor_id == job.account_id