class LegacyId(db.Model): id = db.Column(db.Integer, primary_key=True) legacy_id_provider_id = db.Column(db.Integer, db.ForeignKey(LegacyIdProvider.id)) legacy_id_provider = db.relationship(LegacyIdProvider) number = db.Column(db.Integer, nullable=False) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User) def __repr__(self): return str(self.__class__) + ": " + str(self.__dict__) def __str__(self): return self.prefix + self.zero_filled_number @property def barcode(self): return '{}{}'.format( self.legacy_id_provider.prefix, str(self.number).zfill( self.legacy_id_provider.number_fixed_length), )
class BioresourceId(db.Model): id = db.Column(db.Integer, primary_key=True) bioresource_id_provider_id = db.Column( db.Integer, db.ForeignKey(BioresourceIdProvider.id)) bioresource_id_provider = db.relationship(BioresourceIdProvider) number = db.Column(db.Integer, nullable=False) check_character = db.Column(db.String(1), nullable=False) legacy_number = db.Column(db.Integer) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User) def __repr__(self): return str(self.__class__) + ": " + str(self.__dict__) def __str__(self): return self.full_code @property def full_code(self): return self.bioresource_id_provider.prefix + str( self.number) + self.check_character @property def barcode(self): return self.full_code
class Blinding(db.Model): id = db.Column(db.Integer, primary_key=True) unblind_id = db.Column(db.String(100), nullable=False) blinding_type_id = db.Column(db.Integer, db.ForeignKey(BlindingType.id), nullable=False) blinding_type = db.relationship(BlindingType, backref=db.backref("blindings")) pseudo_random_id_id = db.Column(db.Integer, db.ForeignKey(PseudoRandomId.id), nullable=False) pseudo_random_id = db.relationship(PseudoRandomId) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User) def __repr__(self): return '; '.join([ self.blinding_type.name, self.unblind_id, self.pseudo_random_id.full_code, ]) def __lt__(self, other): return self.name < other.name
class TaskFile(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) filename = db.Column(db.UnicodeText()) local_filepath = db.Column(db.UnicodeText()) task_id = db.Column(db.Integer, db.ForeignKey(Task.id)) task = db.relationship(Task, backref=backref('files', cascade='all, delete-orphan')) field_id = db.Column(db.Integer, db.ForeignKey(Field.id)) field = db.relationship(Field, lazy="joined") def set_filename_and_save(self, file): self.filename = file.filename local_filepath = self._new_local_filepath( filename=file.filename, parent=str(self.task.id), ) self.local_filepath = str(local_filepath) local_filepath.parent.mkdir(parents=True, exist_ok=True) file.save(local_filepath) def _new_local_filepath(self, filename, parent=None): result = pathlib.Path(current_app.config["FILE_UPLOAD_DIRECTORY"]) if parent: result = result.joinpath(secure_filename(parent)) result = result.joinpath( secure_filename("{}_{}".format(uuid.uuid1().hex, filename))) return result
class DemographicsRequestColumn(db.Model): id = db.Column(db.Integer, primary_key=True) demographics_request_id = db.Column(db.Integer, db.ForeignKey(DemographicsRequest.id)) demographics_request = db.relationship( DemographicsRequest, foreign_keys=[demographics_request_id], backref=db.backref("columns")) name = db.Column(db.String(500)) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship( User, foreign_keys=[last_updated_by_user_id]) def __lt__(self, other): return self.name < other.name def __repr__(self): fields = '; '.join([ f'{key}="{value}"' for key, value in self.__dict__.items() if key[0] != '_' ]) return f'[{type(self).__name__}({fields})]'
class ParticipantIdentifierSource(db.Model): __tablename__ = 'participant_identifier_source' id = db.Column(db.Integer, primary_key=True) linked_minimum_patient_identifier_source_id = db.Column( db.Integer, db.ForeignKey("participant_identifier_source.id"), nullable=True) type = db.Column(db.String(100), nullable=False) study_id = db.Column(db.Integer, db.ForeignKey(Study.id), nullable=True) study = db.relationship( Study, backref=db.backref("participant_identifier_sources")) __mapper_args__ = { 'polymorphic_identity': 'participant_identifier_source', 'polymorphic_on': type, } last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User) identifiers = db.relationship( "ParticipantIdentifier", secondary=participant_identifiers__participant_identifier_sources, back_populates="sources", collection_class=set) def __str__(self): return f"{self.type.name}: {self.identifier}"
class TaskAssignedUser(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) task_id = db.Column(db.Integer, db.ForeignKey(Task.id), nullable=False) task = db.relationship(Task, backref="assigned_user_history") user_id = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False) user = db.relationship(User) notes = db.Column(db.String(255))
class Field(db.Model): id = db.Column(db.Integer(), primary_key=True) field_group_id = db.Column(db.Integer(), db.ForeignKey(FieldGroup.id)) field_group = db.relationship(FieldGroup, backref='fields') order = db.Column(db.Integer()) field_type_id = db.Column(db.Integer(), db.ForeignKey(FieldType.id)) field_type = db.relationship(FieldType, lazy="joined") field_name = db.Column(db.String) label = db.Column(db.String) required = db.Column(db.Boolean, default=0) reportable = db.Column(db.Boolean, default=0) max_length = db.Column(db.Integer(), default=0) default = db.Column(db.String, default="") choices = db.Column(db.String, default="") allowed_file_extensions = db.Column(db.String, default="") download_filename_format = db.Column(db.String, default="") validation_regex = db.Column(db.String, default="") description = db.Column(db.UnicodeText, default="") def format_value(self, value): return self.field_type.format_value(value) def data_value(self, value): return self.field_type.data_value(value) def get_default(self): if self.default == '': return None else: return self.default @property def has_choices(self): return self.field_type.has_choices def get_choices(self): if self.field_type.is_boolean: return ['Yes', 'No'] elif not self.choices: return [] else: return [(c, c) for c in self.choices.split("|")] def get_allowed_file_extensions(self): return self.allowed_file_extensions.split("|") def get_label(self): if self.label: return self.label else: return self.field_name def __repr__(self): return 'Field(field_name="{}", order="{}", field_type="{}")'.format( self.field_name, self.order, self.field_type.name )
class StudyParticipant(db.Model): id = db.Column(db.Integer, primary_key=True) study_id = db.Column(db.Integer, db.ForeignKey(Study.id), nullable=False) study = db.relationship(Study, backref=db.backref("participants")) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User)
class TaskStatus(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) task_id = db.Column(db.Integer, db.ForeignKey(Task.id), nullable=False) task = db.relationship(Task, backref="status_history") notes = db.Column(db.String(255)) task_status_type_id = db.Column(db.Integer, db.ForeignKey(TaskStatusType.id), nullable=False) task_status_type = db.relationship(TaskStatusType, backref="assigned_tasks")
class UploadData(db.Model): id = db.Column(db.Integer(), primary_key=True) upload_id = db.Column(db.Integer(), db.ForeignKey(Upload.id)) upload = db.relationship(Upload, backref=db.backref("data")) field_id = db.Column(db.Integer(), db.ForeignKey(Field.id)) field = db.relationship(Field) value = db.Column(db.String) def __repr__(self): items = ("%s = %r" % (k, v) for k, v in self.__dict__.items()) return "<%s: {%s}>" % (self.__class__.__name__, ', '.join(items))
class Task(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(255)) organisation_id = db.Column(db.Integer, db.ForeignKey(Organisation.id)) organisation = db.relationship(Organisation, lazy="joined", backref='tasks') organisation_description = db.Column(db.String(255)) service_id = db.Column(db.Integer, db.ForeignKey(Service.id)) service = db.relationship(Service, lazy="joined", backref='tasks') requestor_id = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False) requestor = db.relationship(User, lazy="joined", backref='tasks', foreign_keys=[requestor_id]) current_status_type_id = db.Column(db.Integer, db.ForeignKey(TaskStatusType.id), nullable=False) current_status_type = db.relationship(TaskStatusType) current_assigned_user_id = db.Column(db.Integer, db.ForeignKey(User.id), nullable=True) current_assigned_user = db.relationship( User, foreign_keys=[current_assigned_user_id]) @property def long_name(self): return "{}: {}".format(self.service.name, self.name) @property def total_todos(self): return len(self.todos) @property def required_todos(self): return len([t for t in self.todos if t.is_required]) @property def complete_todos(self): return len([t for t in self.todos if t.is_complete]) @property def notification_email_addresses(self): return self.service.notification_email_addresses + [ self.requestor.email ] def get_data_for_task_id(self, field_id): return next((t for t in self.data if t.field_id == field_id), None)
class DemographicsRequestColumnDefinition(db.Model): id = db.Column(db.Integer, primary_key=True) demographics_request_id = db.Column(db.Integer, db.ForeignKey(DemographicsRequest.id)) demographics_request = db.relationship( DemographicsRequest, foreign_keys=[demographics_request_id], back_populates="column_definition") last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship( User, foreign_keys=[last_updated_by_user_id]) uhl_system_number_column_id = db.Column( db.Integer, db.ForeignKey(DemographicsRequestColumn.id)) uhl_system_number_column = db.relationship( DemographicsRequestColumn, foreign_keys=[uhl_system_number_column_id]) nhs_number_column_id = db.Column( db.Integer, db.ForeignKey(DemographicsRequestColumn.id)) nhs_number_column = db.relationship(DemographicsRequestColumn, foreign_keys=[nhs_number_column_id]) family_name_column_id = db.Column( db.Integer, db.ForeignKey(DemographicsRequestColumn.id)) family_name_column = db.relationship(DemographicsRequestColumn, foreign_keys=[family_name_column_id]) given_name_column_id = db.Column( db.Integer, db.ForeignKey(DemographicsRequestColumn.id)) given_name_column = db.relationship(DemographicsRequestColumn, foreign_keys=[given_name_column_id]) gender_column_id = db.Column(db.Integer, db.ForeignKey(DemographicsRequestColumn.id)) gender_column = db.relationship(DemographicsRequestColumn, foreign_keys=[gender_column_id]) dob_column_id = db.Column(db.Integer, db.ForeignKey(DemographicsRequestColumn.id)) dob_column = db.relationship(DemographicsRequestColumn, foreign_keys=[dob_column_id]) postcode_column_id = db.Column(db.Integer, db.ForeignKey(DemographicsRequestColumn.id)) postcode_column = db.relationship(DemographicsRequestColumn, foreign_keys=[postcode_column_id]) gender_female_value = db.Column(db.String) gender_male_value = db.Column(db.String) @property def is_valid(self): return (self.nhs_number_column_id is not None and self.dob_column_id is not None) or (self.family_name_column_id is not None and self.given_name_column_id is not None and self.dob_column_id is not None and self.gender_column_id is not None and self.postcode_column_id is not None) or ( self.uhl_system_number_column_id is not None)
class Upload(db.Model): id = db.Column(db.Integer(), primary_key=True) study_id = db.Column(db.Integer(), db.ForeignKey(Study.id)) study_number = db.Column(db.String(20)) uploader_id = db.Column(db.Integer(), db.ForeignKey(User.id)) date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) study = db.relationship(Study, backref=db.backref("uploads")) uploader = db.relationship(User) completed = db.Column(db.Boolean, default=0) deleted = db.Column(db.Boolean, default=0)
class BlindingSet(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) study_id = db.Column(db.Integer, db.ForeignKey(Study.id), nullable=False) study = db.relationship(Study, backref=db.backref("blinding_sets")) def __repr__(self): return self.name def __lt__(self, other): return self.name < other.name def get_blind_ids(self, unblind_id, user): result = [] for bt in self.blinding_types: blind_id = bt.get_blind_id(unblind_id, user) if blind_id: result.append(blind_id) return result def get_unblind_id(self, blind_id): for bt in self.blinding_types: unblind_id = bt.get_unblind_id(blind_id) if unblind_id: return unblind_id
class ApiKey(db.Model): id = db.Column(db.Integer, primary_key=True) key = db.Column(GUID, nullable=False, default=uuid.uuid4) user_id = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False) user = db.relationship(User, backref=db.backref("api_key", uselist=False)) def __repr__(self): return f'API Key for User {self.user.full_name}'
class TaskData(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) value = db.Column(db.UnicodeText()) task_id = db.Column(db.Integer, db.ForeignKey(Task.id)) task = db.relationship(Task, backref=backref('data', cascade='all, delete-orphan')) field_id = db.Column(db.Integer, db.ForeignKey(Field.id)) field = db.relationship(Field, lazy="joined") @property def formated_value(self): return self.field.format_value(self.value) @property def data_value(self): return self.field.data_value(self.value)
class AbstractSection(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) publication_id = db.Column(db.Integer(), db.ForeignKey(Publication.id)) publication = db.relationship(Publication, lazy="joined", backref='abstracts') label = db.Column(db.String(200)) text = db.Column(db.UnicodeText())
class PseudoRandomId(db.Model): id = db.Column(db.Integer, primary_key=True) pseudo_random_id_provider_id = db.Column( db.Integer, db.ForeignKey(PseudoRandomIdProvider.id)) pseudo_random_id_provider = db.relationship(PseudoRandomIdProvider) ordinal = db.Column(db.Integer, nullable=False) unique_code = db.Column(db.Integer, nullable=False) check_character = db.Column(db.String(1), nullable=False) full_code = db.Column(db.String(20), nullable=False) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User) @property def barcode(self): return self.full_code
class LabelPack(db.Model): id = db.Column(db.Integer, primary_key=True) type = db.Column(db.String(100), nullable=False) study_id = db.Column(db.Integer, db.ForeignKey(Study.id)) study = db.relationship(Study, backref=db.backref("label_packs")) __mapper_args__ = { "polymorphic_identity": "Pack", "polymorphic_on": type, } def user_defined_participant_id(self): return False def allow_batch_printing(self): return True @property def name(self): return re.sub('([a-z])([A-Z])', r'\1 \2', self.__class__.__name__) def print(self, count): for _ in range(count): current_app.logger.info( f'Printing label for study {self.study.name}') self._do_print() db.session.commit() time.sleep(current_app.config['PRINTING_SET_SLEEP']) def save_participant_id(self, participant_id): pit = ParticipantIdentifierType.get_study_participant_id() pi = ParticipantIdentifier.query.filter_by( participant_identifier_type_id=pit.id, identifier=participant_id, ).one_or_none() if pi is None: db.session.add( ParticipantIdentifier( participant_identifier_type_id=pit.id, identifier=participant_id, last_updated_by_user_id=current_user.id, )) def _do_print(self, participant_id=None): pass def __repr__(self): return self.name def __lt__(self, other): return self.name < other.name
class BlindingType(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) blinding_set_id = db.Column(db.Integer, db.ForeignKey(BlindingSet.id), nullable=False) blinding_set = db.relationship(BlindingSet, backref=db.backref("blinding_types")) pseudo_random_id_provider_id = db.Column(db.Integer, db.ForeignKey( PseudoRandomIdProvider.id), nullable=False) pseudo_random_id_provider = db.relationship(PseudoRandomIdProvider) deleted = db.Column(db.Boolean, nullable=False, default=False) duplicate_number = db.Column(db.Integer, default=0, nullable=False) def __repr__(self): return self.name def __lt__(self, other): return self.name < other.name def get_blind_id(self, unblind_id, user): blinding = (Blinding.query.filter_by( blinding_type_id=self.id).filter_by(unblind_id=unblind_id).first()) if not blinding: pseudo_random_id = self.pseudo_random_id_provider.allocate_id(user) blinding = Blinding( unblind_id=unblind_id, blinding_type=self, pseudo_random_id=pseudo_random_id, last_updated_by_user=user, ) return blinding def get_unblind_id(self, blind_id): blinding = (Blinding.query.filter_by( blinding_type_id=self.id).join(PseudoRandomId).filter_by( full_code=blind_id).first()) return blinding
class LabelParticipantIdentifierSource(ParticipantIdentifierSource): __tablename__ = 'label_participant_identifier_source' id = db.Column(db.Integer, db.ForeignKey('participant_identifier_source.id'), primary_key=True) __mapper_args__ = { 'polymorphic_identity': 'label_participant_identifier_source', }
class ParticipantIdentifier(db.Model): __tablename__ = 'participant_identifier' id = db.Column(db.Integer, primary_key=True) identifier = db.Column(db.String(100), nullable=False) participant_identifier_type_id = db.Column( db.Integer, db.ForeignKey(ParticipantIdentifierType.id)) participant_identifier_type = db.relationship(ParticipantIdentifierType) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User) sources = db.relationship( "ParticipantIdentifierSource", secondary=participant_identifiers__participant_identifier_sources, back_populates="identifiers", collection_class=set) def __str__(self): return f"{self.type.name}: {self.identifier}"
class UploadFile(db.Model): id = db.Column(db.Integer(), primary_key=True) upload_id = db.Column(db.Integer(), db.ForeignKey(Upload.id)) upload = db.relationship(Upload, backref=db.backref("files")) field_id = db.Column(db.Integer(), db.ForeignKey(Field.id)) field = db.relationship(Field) filename = db.Column(db.String(500)) def get_download_filename(self): if len(self.field.download_filename_format or '') == 0: return self.filename else: return self.field.download_filename_format.format( file=self) + os.path.splitext(self.filename)[-1] def filepath(self): return os.path.join( secure_filename("{}_{}".format(self.upload.study.id, self.upload.study.name)), secure_filename("{}_{}_{}".format(self.id, self.upload.study_number, self.filename)), )
class Author(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) publication_id = db.Column(db.Integer(), db.ForeignKey(Publication.id)) publication = db.relationship(Publication, lazy="joined", backref='authors') last_name = db.Column(db.String(100)) fore_name = db.Column(db.String(100)) initials = db.Column(db.String(100)) affiliation = db.Column(db.UnicodeText()) @property def full_name(self): return f'{self.fore_name} {self.last_name}'
class Study(db.Model): id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(100)) date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) allow_duplicate_study_number = db.Column(db.Boolean, nullable=False, default=False) allow_empty_study_number = db.Column(db.Boolean, nullable=False, default=False) study_number_format = db.Column(db.String(50)) study_number_name = db.Column(db.String(100)) field_group_id = db.Column(db.Integer(), db.ForeignKey(FieldGroup.id)) field_group = db.relationship(FieldGroup, backref=db.backref("study")) owners = db.relationship( User, secondary=studies_owners, backref=db.backref("owned_studies", lazy="dynamic"), ) collaborators = db.relationship( User, secondary=studies_collaborators, backref=db.backref("collaborator_studies", lazy="dynamic"), ) def __str__(self): return self.name @property def upload_count(self): return len([u for u in self.uploads if not u.deleted]) def upload_count_for_user(self, user): return len( [u for u in self.uploads if not u.deleted and u.uploader == user]) @property def outstanding_upload_count(self): return len( [u for u in self.uploads if not u.deleted and not u.completed]) def get_study_number_name(self): return self.study_number_name or 'Study Number'
class DemographicsRequestDataMessage(db.Model): id = db.Column(db.Integer, primary_key=True) demographics_request_data_id = db.Column( db.Integer, db.ForeignKey(DemographicsRequestData.id)) demographics_request_data = db.relationship(DemographicsRequestData, backref=db.backref("messages")) type = db.Column(db.String) source = db.Column(db.String) scope = db.Column(db.String) message = db.Column(db.String) created_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) @property def is_error(self): return self.type == 'error'
class LegacyIdProvider(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) prefix = db.Column(db.String(10), nullable=False) number_fixed_length = db.Column(db.Integer, nullable=False) last_updated_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) last_updated_by_user_id = db.Column(db.Integer, db.ForeignKey(User.id)) last_updated_by_user = db.relationship(User) def allocate_id(self, user): found = 1 tries = 0 prospective_id = 0 while found > 0: prospective_id = random.randint(1, 900000) tries = tries + 1 found = LegacyId.query.filter( LegacyId.legacy_id_provider_id == self.id).filter( LegacyId.number == prospective_id).count() actual_id = LegacyId( legacy_id_provider=self, number=prospective_id, last_updated_by_user=user, ) db.session.add(actual_id) return actual_id def allocate_ids(self, count, user): result = [] for _ in range(count): result.append(self.allocate_id(user)) return result def __repr__(self): return str(self.__class__) + ": " + str(self.__dict__)
class ToDo(AuditMixin, CommonMixin, db.Model): OUTSTANDING = 'Outstanding' COMPLETED = 'Completed' NOT_REQUIRED = 'Not Required' _status_map = { -1: NOT_REQUIRED, 0: OUTSTANDING, 1: COMPLETED, } @staticmethod def get_status_code_from_name(name): return {v: k for k, v in ToDo._status_map.items()}[name] id = db.Column(db.Integer(), primary_key=True) task_id = db.Column(db.Integer, db.ForeignKey(Task.id)) task = db.relationship(Task, backref='todos') description = db.Column(db.UnicodeText()) status = db.Column(db.Integer, db.CheckConstraint("status IN (-1, 0, 1)"), nullable=False, default=0) @property def status_name(self): return ToDo._status_map[self.status] @property def is_outstanding(self): return self.status == 0 @property def is_required(self): return self.status > -1 @property def is_complete(self): return self.status == 1
class Service(AuditMixin, CommonMixin, db.Model): id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(255)) generic_recipients = db.Column(db.String(255)) suppress_owner_email = db.Column(db.Boolean) field_group_id = db.Column(db.Integer, db.ForeignKey(FieldGroup.id)) field_group = db.relationship(FieldGroup) introduction = db.Column(db.UnicodeText()) def __str__(self): return self.name def get_field_for_field_name(self, field_name): if self.field_group: return self.field_group.get_field_for_field_name(field_name) @property def notification_email_addresses(self): return list( filter(len, [ r.email for r in self.owners if not self.suppress_owner_email ] + re.split(r'[;,\s]+', self.generic_recipients or '')))