def not_empty(key, value): """ Verify if the value is empty or not. Raises validation error if empty Raises error if no value :param key: key value where the value is from :param value: value :return: None """ if not value: raise err.ValidationError({key: "Value cannot be empty"}) if isinstance(value, str) and not value.strip(): raise err.ValidationError({key: "Value cannot be empty"})
def save(self, *args, **kwargs): """ Clean the data before saving to the database also generate api key if no key :param args: :param kwargs: :return: """ self.username = self.normalize_username(self.username) self.work_email = BaseUserManager.normalize_email(self.work_email) if self.reporting_manager: try: _reporting_manager = Employee.get_employee_by_email( self.reporting_manager) except err.NotFoundError: log.error(self.reporting_manager) raise err.ValidationError({ "reporting_manager": "Given reporting manager not available" }) if _reporting_manager.id == self.id: raise err.ValidationError({ "reporting_manager": "You cannot assign yourself as reporting manager" }) self.reporting_manager = _reporting_manager.id if not hasattr(self, "_api_key") or not getattr(self, "_api_key"): log.info("Creating a new api key") api_key, key = APIKey.objects.create_key(name=self.work_email) self._api_key = APIKey.objects.get_from_key(key) _fields = ("employee_id", ) for _field in _fields: val = getattr(self, _field) if val: setattr(self, _field, val.lower().strip()) if not self.graduated_year: self.graduated_year = None # TODO default array doesnt work - Hack if not getattr(self, "skills"): self.skills = list() return super(self.__class__, self).save(*args, **kwargs)
def _search_employee_for_pagination(app_context, data_dict): """ This function is for internal use only. Utilized by views for pagination :return: Elastic search document """ auth.is_authenticated(app_context) search_fields = data_dict.get('search_fields', '') search_value = data_dict.get('q', '') query_dict = data_dict.get('query_dict', '') if query_dict and not isinstance(query_dict, dict): raise err.ValidationError( {"raw_query": "Raw query should be of type json"}) if search_fields and not isinstance(search_fields, list): try: search_fields = search_fields.split(",") if not search_fields: raise err.ValidationError( {"search_fields": "Search fields should be list"}) except Exception as e: log.error(e) raise err.ValidationError({ "search_fields": "Something wrong while converting text to list in search_fields" }) if not search_value and not query_dict: log.info("No search value given, returning all with limit") profiles = EmployeeDocument.search() else: if query_dict: log.info("given query dict for searching") profiles = EmployeeDocument.search().from_dict(query_dict) else: log.info("Given search value: {}".format(search_value)) if not search_fields: search_fields = EmployeeDocument.default_search_fields_full_text( ) profiles = EmployeeDocument.search().query('multi_match', query=search_value, fields=search_fields) return profiles
def search_employee(app_context, data_dict): """ This api provides a way to search for an employee by - first name, - middle name - last name - work email - employee id - employee position - country Max limit is 100. If no search_fields are give - full text search is performed. Employee can also be searched from elastic search dictionary using parameter raw_query :parameter search_fields: list Fields to perform search operation, if not given defaulkt value is used search: str Value to be searched offset: int Offset limit: int max value is 100 :param app_context: dict application context :param data_dict: dict :return: dict """ limit = data_dict.get('limit', 20) offset = data_dict.get('offset', 0) if not isinstance(offset, int): raise err.ValidationError({'offset': "Offset should be integer"}) if not isinstance(limit, int) or limit > 100: raise err.ValidationError( {"limit": "Should be integer and max value allowed is 100"}) # Authorization is checked in this function profiles = _search_employee_for_pagination(app_context, data_dict) results = {"results": [], "res_count": profiles.count()} for _p in profiles[offset:limit]: results['results'].append(_p.as_dict()) return results
def number_validator(key, value): """ Check if the given value is number :param key: str :param value: str :return: None """ try: int(value) except Exception as e: raise err.ValidationError({key: "Value must be integer"})
def country_code_validator(key, value): """ Validate the given country code. Country code should follow (ISO 3166) standards :param key: str :param value: str :return: None """ try: _country = pycountry.countries.get(alpha_2=value) if not _country: raise err.ValidationError({ key: "Not a valid country code - should follow ISO 3166 standard." }) except Exception as e: log.error(e) raise err.ValidationError({ key: "Something wrong with given country code - should follow ISO 3166 standard." })
def validate_existing_user(key, value): """ See if the user already exists. If exists raise an error existing user :return: """ try: _employee = models.Employee.get_employee_by_id(value) raise err.ValidationError({ key: "User with employee id already exists" }) except err.NotFoundError: pass
def validate_avatar_file(file_upload): """ Validate the avatar uploaded or updated for an employee :param file_upload: class UploadedFile :return: Boolean """ _allowed_formats = tuple(config.get('AVATAR_FILE_FORMATS', ["png", "jpeg", "jpg"])) file_name, file_ext = os.path.splitext(file_upload.name) if not file_ext or file_ext.replace(".", "").lower() not in _allowed_formats: raise err.ValidationError({ "upload_avatar": "File not in required format only allowed format is png, jpeg and jpg" }) if file_upload.size > int(config.get('MAX_AVATAR_SIZE_BYTES')): raise err.ValidationError({ "avatar": "Uploaded image file exceeded limit." }) return True
def url_validator(key, value): """ :param key: :param value: :return: """ try: _val = py_validators.url(value) if not _val: raise Exception except Exception as e: raise err.ValidationError({key: "Not a valid url - use full url"})
def email_validator(key, value): """ Verify if the given email id is valid or not. Raises a validation error if not valid. :param key: key value where the value is from :param value: value should be of type email :return: None """ try: validate_email(value) except EmailNotValidError as e: raise err.ValidationError( {key: "Not a valid email. Please verify your email"})
def check_if_user_exists(key, value): """ Check if the given employee exists :param key: :param value: :return: """ try: _employee = models.Employee.get_employee_by_id(value) except err.NotFoundError: raise err.ValidationError( { key: "Given employee not found" } )
def show_employee(app_context, data_dict): """ Show employee for the given employee id or employee unique identifier. Note: Access control is given is schema in show attribute - space separated string e.g. admin hr member all - admin: show to only to admin - admin hr: show admin and to hr - admin hr member: show admin and to hr and user profile (logged in user == requested user profile) - all: show to every logged in user :param app_context: dict :param data_dict: dict (containing employee_id oir id) :return: dict """ log.info("Show employee api...") auth.employee_show(app_context) _user = app_context.get('user') role = app_context.get('role') _id = data_dict.get('id', '') _schema = schemas.get_schema('employee_schema') log.info("Showing the employee data for id: {}".format(_id)) if not _id: raise err.ValidationError({"id": "id parameter is required."}) _employee = models.Employee.get_employee_by_id(_id) # If logged in user and requesting profile are not same if role == "member": if _user.id != _employee.id: role = "all" _data = _employee.as_dict() response = dict() for _property in _schema['properties']: _show = _schema['properties'][_property].get('show', "").split(" ") if role in _show: response[_property] = _data[_property] for x in ("id", "created_at", "updated_at", "avatar"): response[x] = _data[x] return response
def send_email(subject=None, body=None, to=None): """ Send an email given subject body and user list :param subject: str Email subject :param body: str Email body :param to: str To who? :return: None """ log.info("Sending email to: ") log.info(to) for _email in to: validators.email_validator(_email) if not subject or not body: raise err.ValidationError("Subject or body is required parameter") email = EmailMessage(subject, body, to=to) email.send()
def date_validator(key, value): """ Verify the given date string. The date string should be of format %Y-%m-%d (ISO). Note: string should be only of date type. Cannot accept datetime string. Raises error if does not match the format. :param key: key value where the value is from :param value: value shoudl be of type date string :return: None """ try: datetime.strptime(value, "%Y-%m-%d") except ValueError: raise err.ValidationError({ key: "Given date string does not match the format %Y-%m-%d. Should be ISO format" })
def delete_employee(app_context, data_dict): """ Delete an employee given employee_id or unique id. Only admin or hr can delete employee. :param app_context: :param data_dict: :return: """ auth.employee_delete(app_context) _id = data_dict.get('id', '') log.info("Deleting employee for an id: {}".format(id)) if not _id: raise err.ValidationError({"id": "id parameter is required."}) _employee = models.Employee.get_employee_by_id(_id) _employee.delete() return { "message": "Employee with id: {} has been deleted successfully".format(_id) }
def generate_api_key(cls, email): """ Generate API key given the user/work email :param email: str :return: tuple """ try: _profile = cls.objects.get(work_email=email) except Employee.DoesNotExist: raise err.ValidationError( {"work_email": "No user found for the given work email"}) log.info("Generating API key for an email: {}".format(email)) try: APIKey.objects.filter(name=email).delete() except Exception as e: log.error(e) pass api_key, key = APIKey.objects.create_key(name=email) _profile._api_key = APIKey.objects.get_from_key(key) _profile.save() return api_key, key
def send_email_create_employee(to): """ Sends Subject and body to the recently created employee :return: tuple """ log.info("Sending an email to the new employee") log.info(to) if isinstance(to, list) or isinstance(to, tuple): if len(to) > 1: raise err.ValidationError( "This should not happen. On create employee, email is sent to employee only" ) validators.email_validator("work_email", to[0]) user = u.get_user_given_email(to[0]) sub = "Admin has created your profile. Please reset your password" body = """ Hi {first_name} {last_name}, Welcome to {application_name}!! Please use this link to reset your password and login to your account to update your profile. Password reset link:{reset_link} Yours, Admin, {company_name} """.format(first_name=user.first_name, last_name=user.last_name, application_name=config.get("APPLICATION_TITLE"), reset_link="", company_name=config.get("COMPANY_NAME")) return sub, body
def update_employee(app_context, data_dict): """ Update employee profile. Parameter should contain id - representing unique id of the employee of id as employee id. Only admin/hr can alter employee fields or logged in user can alter their fields. Note: Access control is given in schema in update attribute - space separated string e.g. admin hr member all - admin: Only admin can update - admin hr: Only admin and to hr can update - admin hr member: admin , hr and member can update (logged in user == requested user profile) - all: show to every logged in user :param app_context: dict :param data_dict: dict (containing fields to be updated) :return: dict success message or show_employee - api """ auth.is_authenticated(app_context) role = app_context.get('role') _show_employee = u.core_convert_to_bool( data_dict.pop('show_employee', True)) _id = data_dict.pop('id', None) _files = app_context.get('files', '') _schema = schemas.get_schema('employee_schema') log.info("Updating an employee for an id: {}".format(_id)) if data_dict.get('skills', ''): _skills = data_dict['skills'] if isinstance(_skills, str): # Hack for multipart/form-data?? Dont know if this is the right way # TODO: need to create a data insert pre processing pipeline data_dict['skills'] = [ s.strip().title() for s in _skills.split(",") ] if role == "all": err.ValidationError({"InternalError": "This should not occur."}) if not _id: raise err.ValidationError({"id": "id parameter is required."}) try: with model_transaction.atomic(): _employee = models.Employee.get_employee_by_id(_id) # Check authorization auth.employee_update({ "role": app_context.get('role'), "user": app_context.get('user'), "employee": _employee }) # Internal user only _data = _employee._as_dict() log.info("Updating the data and validating") _data.update(data_dict) schemas.validate("employee_schema", _data) # Insert operation _emp_extras = models.EmployeeExtras.objects.filter( employee=_employee) _extras = [] for _key in data_dict: if _key in _schema['properties']: try: _property = _schema['properties'][_key] _update = _property.get('update', "").split(" ") if role not in _update: raise err.NotAuthorizedError( {_key: "Not authorized to update the value"}) if hasattr(_employee, _key): setattr(_employee, _key, data_dict.get(_key)) else: for _emp_ext in _emp_extras: if _emp_ext.key == _key: _emp_ext.value = data_dict.get(_key) _extras.append(_emp_ext) break except KeyError as e: log.warning(e) pass if 'upload_avatar' in _files: log.info("Found avatar update..") _uploaded_avatar = _files['upload_avatar'] if _uploaded_avatar: validators.validate_avatar_file(_uploaded_avatar) if _employee.avatar and os.path.isfile(_employee.avatar.path): os.remove(_employee.avatar.path) _employee.avatar = _uploaded_avatar _employee.save() for _md in _extras: _md.save() except Exception as e: log.error(e) raise e if _show_employee: result = show_employee(app_context, {"id": _employee.employee_id}) else: result = { "message": "Employee with id: {} has been updated successfully".format( _employee.employee_id) } return result
def post(self, request, profile_id=None): """ :param request: :param profile_id: :return: """ if profile_id == request.user.id or not profile_id: raise err.ValidationError({ "profile": "You cannot delete your own profile. Please contact HR or Admin" }) context = { "type": "profile", "user": request.user, "is_superuser": request.user.is_superuser, "role": request.user.role } extra_vars = dict() extra_vars['errors'] = dict() try: auth.employee_create(context) api_action.post_actions("delete_employee", context, {"id": profile_id}) except err.ValidationError as e: extra_vars['errors'].update(e.args[0]) u.prepare_error_data(extra_vars['errors']) log.error(e) messages.error( request, "<br>".join([ "{}: {}".format(key, value) for key, value in extra_vars['errors'].items() ])) if profile_id: return redirect('other_profile', profile_id=profile_id) else: return redirect('profile') except err.NotAuthorizedError: messages.error(request, 'You are not authorized to perform this action') log.info("Logged in user not authorized to edit view") return abort( request, code=401, error_message="You are not authorized to see this page", error_title="Not Authorized") except (err.UserNotFound, err.NotFoundError) as e: log.info(e) return abort(request, code=404, error_message="User Not found.", error_title="Not Found") except Exception as e: log.error(e) return server_error(request, code=500, error_message="Server Error. Something wrong") return redirect('profile_search')