Example #1
0
class MeasurementUnit(models.Model):
    """Defines a unit type and metadata on measurement values."""
    class Meta:
        db_table = "measurement_unit"

    unit_name = VarCharField(
        help_text=_("Name for unit of measurement."),
        unique=True,
        verbose_name=_("Name"),
    )
    display = models.BooleanField(
        default=True,
        help_text=_(
            "Flag indicating the units should be displayed along with values."
        ),
        verbose_name=_("Display"),
    )
    alternate_names = VarCharField(
        blank=True,
        help_text=_("Alternative names for the unit."),
        null=True,
        verbose_name=_("Alternate Names"),
    )
    type_group = VarCharField(
        choices=MeasurementType.Group.GROUP_CHOICE,
        default=MeasurementType.Group.GENERIC,
        help_text=_("Type of measurement for which this unit is used."),
        verbose_name=_("Group"),
    )

    # TODO: this should be somehow rolled up into the unit definition
    conversion_dict = {
        "g/L": lambda y, metabolite: 1000 * y / metabolite.molar_mass,
        "mg/L": lambda y, metabolite: y / metabolite.molar_mass,
        "µg/L": lambda y, metabolite: y / 1000 / metabolite.molar_mass,
        "Cmol/L": lambda y, metabolite: 1000 * y / metabolite.carbon_count,
        "mol/L": lambda y, metabolite: 1000 * y,
        "uM": lambda y, metabolite: y / 1000,
        "mol/L/hr": lambda y, metabolite: 1000 * y,
        "mM": lambda y, metabolite: y,
    }

    def to_json(self):
        return {"id": self.pk, "name": self.unit_name}

    def __str__(self):
        return self.unit_name
Example #2
0
File: models.py Project: JBEI/edd
class Institution(models.Model):
    """An institution to associate with EDD user profiles."""
    class Meta:
        db_table = "profile_institution"

    institution_name = VarCharField()
    description = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.institution_name
Example #3
0
class Category(models.Model):
    """
    Groupings of types of data to load into EDD.

    Splitting the various file layouts and protocols into higher level
    groupings allows better navigation for users to select the specific loading
    process they need.
    """
    class Meta:
        ordering = ("sort_key", )
        verbose_name_plural = "Categories"

    layouts = models.ManyToManyField(
        Layout,
        through="CategoryLayout",
        help_text=_("Supported input layouts for this load category."),
        verbose_name=_("File layouts"),
        related_name="load_category",
    )
    protocols = models.ManyToManyField(
        edd_models.Protocol,
        help_text=_("Protocols that appear in this load category."),
        verbose_name=_("Protocols"),
        related_name="load_category",
    )
    name = VarCharField(help_text=_("Name of this loading category."),
                        verbose_name=_("Name"))
    type_group = VarCharField(
        blank=True,
        help_text=_(
            "Constrains measurement types searched during data loading."),
        null=True,
        verbose_name=_("Measurement type group"),
    )
    sort_key = models.PositiveIntegerField(
        null=False,
        unique=True,
        help_text=_("Relative order this category is displayed during load."),
        verbose_name=_("Display order"),
    )

    def __str__(self):
        return self.name
Example #4
0
class MetaboliteSpecies(models.Model):
    """ Mapping for a metabolite to an species defined by a SBML template. """
    class Meta:
        db_table = "measurement_type_to_species"
        index_together = (
            # index implied by unique, making explicit
            ("sbml_template", "species"), )
        unique_together = (
            ("sbml_template", "species"),
            ("sbml_template", "measurement_type"),
        )

    sbml_template = models.ForeignKey(
        SBMLTemplate,
        help_text=_(
            "The SBML Model defining this species link to a Measurement Type."
        ),
        on_delete=models.CASCADE,
        verbose_name=_("SBML Model"),
    )
    measurement_type = models.ForeignKey(
        MeasurementType,
        blank=True,
        help_text=_("Mesurement type linked to this species in the model."),
        null=True,
        on_delete=models.SET_NULL,
        verbose_name=_("Measurement Type"),
    )
    species = VarCharField(
        help_text=_("Species name used in the model for this metabolite."),
        verbose_name=_("Species"),
    )
    short_code = VarCharField(
        blank=True,
        default="",
        help_text=_("Short code used for a species in the model."),
        null=True,
        verbose_name=_("Short Code"),
    )

    def __str__(self):
        return self.species
Example #5
0
class MetaboliteExchange(models.Model):
    """Mapping for a metabolite to an exchange defined by a SBML template."""
    class Meta:
        db_table = "measurement_type_to_exchange"
        index_together = (
            # reactants not unique, but should be searchable
            ("sbml_template", "reactant_name"),
            # index implied by unique, making explicit
            ("sbml_template", "exchange_name"),
        )
        unique_together = (
            ("sbml_template", "exchange_name"),
            ("sbml_template", "measurement_type"),
        )

    sbml_template = models.ForeignKey(
        SBMLTemplate,
        help_text=_("The SBML Model containing this exchange reaction."),
        on_delete=models.CASCADE,
        verbose_name=_("SBML Model"),
    )
    measurement_type = models.ForeignKey(
        MeasurementType,
        blank=True,
        help_text=_(
            "Measurement type linked to this exchange reaction in the model."),
        null=True,
        on_delete=models.CASCADE,
        verbose_name=_("Measurement Type"),
    )
    reactant_name = VarCharField(
        help_text=_("The reactant name used in for this exchange reaction."),
        verbose_name=_("Reactant Name"),
    )
    exchange_name = VarCharField(
        help_text=_("The exchange name used in the model."),
        verbose_name=_("Exchange Name"),
    )

    def __str__(self):
        return self.exchange_name
Example #6
0
class ParserMapping(models.Model):
    """
    Maps incoming layout and MIME to the appropriate Parser class.

    Represents a mime type-specific parser for a given file layout, e.g. a
    different parser for each of Excel, CSV for a single file layout.
    """
    class Meta:
        verbose_name_plural = "Parsers"
        unique_together = ("layout", "mime_type")

    layout = models.ForeignKey(Layout,
                               on_delete=models.CASCADE,
                               related_name="parsers")
    mime_type = VarCharField(help_text=_("Mime type"),
                             verbose_name=_("Mime type"))
    parser_class = VarCharField(help_text=_("Parser class"),
                                verbose_name=_("Parser"))

    def create_parser(self, uuid):
        try:
            # split fully-qualified class name into module and class names
            module_name, class_name = self.parser_class.rsplit(sep=".",
                                                               maxsplit=1)
            # instantiate the parser.
            module = importlib.import_module(module_name)
            parser_class = getattr(module, class_name)
            return parser_class(uuid)
        except Exception as e:
            reporting.raise_errors(
                uuid,
                exceptions.BadParserError(details=_(
                    "Unable to instantiate parser class {parser_class}. "
                    "The problem was {problem}").format(
                        parser_class=self.parser_class, problem=str(e))),
            )

    def __str__(self):
        return f"{self.mime_type}::{self.parser_class}"
Example #7
0
File: metadata.py Project: JBEI/edd
class MetadataGroup(models.Model):
    """Group together types of metadata with a label."""
    class Meta:
        db_table = "metadata_group"

    group_name = VarCharField(
        help_text=_("Name of the group/class of metadata."),
        unique=True,
        verbose_name=_("Group Name"),
    )

    def __str__(self):
        return self.group_name
Example #8
0
class Datasource(models.Model):
    """
    Defines an outside source for bits of data in the system. Initially
    developed to track where basic metabolite information originated
    (e.g. BIGG, KEGG, manual input).
    """

    name = VarCharField(
        help_text=_("The source used for information on a measurement type."),
        verbose_name=_("Datasource"),
    )
    url = VarCharField(blank=True,
                       default="",
                       help_text=_("URL of the source."),
                       verbose_name=_("URL"))
    download_date = models.DateField(
        auto_now=True,
        help_text=_("Date when information was accessed and copied."),
        verbose_name=_("Download Date"),
    )
    created = models.ForeignKey(
        Update,
        editable=False,
        help_text=_("Update object logging the creation of this Datasource."),
        on_delete=models.PROTECT,
        related_name="datasource",
        verbose_name=_("Created"),
    )

    def __str__(self):
        return f"{self.name} <{self.url}>"

    def save(self, *args, **kwargs):
        if self.created_id is None:
            update = kwargs.get("update", None)
            if update is None:
                update = Update.load_update()
            self.created = update
        super().save(*args, **kwargs)
Example #9
0
File: models.py Project: JBEI/edd
class MeasurementNameTransform(models.Model):
    class Meta:
        db_table = "measurement_name_transform"

    input_type_name = VarCharField(
        help_text=_("Name of this Measurement Type in input."),
        verbose_name=_("Input Measurement Type"),
    )

    edd_type_name = models.ForeignKey(
        edd_models.MeasurementType,
        on_delete=models.deletion.CASCADE,
        verbose_name=_("EDD Type Name"),
    )
    parser = VarCharField(blank=True, null=True)

    def to_json(self):
        return {
            "id": self.pk,
            "input_type_name": self.input_type_name,
            "edd_type_name": self.edd_type_name.type_name,
            "parser": self.parser,
        }
Example #10
0
class Layout(models.Model):
    """
    Represents an input file layout for EDD imports.

    Having a DB model for this data allows different EDD deployments to add in
    custom parsers and configure them via the admin app.
    """

    name = VarCharField(help_text=_("Name of this file layout."),
                        verbose_name=_("Name"))
    description = models.TextField(
        blank=True,
        help_text=_("Description of this object."),
        null=True,
        verbose_name=_("Description"),
    )

    def __str__(self):
        return self.name
Example #11
0
class CampaignMembership(models.Model):
    """A link between a Campaign and Study."""
    class Status:
        ACTIVE = "a"
        COMPLETE = "c"
        ABANDONED = "z"
        CHOICE = (
            (ACTIVE, _("Active")),
            (COMPLETE, _("Complete")),
            (ABANDONED, _("Abandoned")),
        )

    campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
    study = models.ForeignKey(edd_models.Study, on_delete=models.CASCADE)
    status = VarCharField(
        choices=Status.CHOICE,
        default=Status.ACTIVE,
        help_text=_("Status of a Study in the linked Campaign."),
    )
Example #12
0
File: models.py Project: JBEI/edd
class UserProfile(models.Model):
    """Additional profile information on a user."""
    class Meta:
        db_table = "profile_user"

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE)
    initials = VarCharField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)
    institutions = models.ManyToManyField(Institution, through="InstitutionID")
    preferences = models.JSONField(blank=True, default=dict)
    approved = models.BooleanField(
        default=False,
        help_text=_(
            "Flag showing if this account has been approved for login."),
        verbose_name=_("Approved"),
    )

    def __str__(self):
        return str(self.user)
Example #13
0
File: models.py Project: JBEI/edd
class InstitutionID(models.Model):
    """
    A link to an Institution with an (optional) identifier; e.g. JBEI with LBL
    employee ID number.
    """
    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["profile", "sort_key"],
                                    name="profile_institution_ordering_idx"),
        ]
        db_table = "profile_institution_user"

    institution = models.ForeignKey(Institution, on_delete=models.CASCADE)
    profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
    identifier = VarCharField(blank=True, null=True)
    sort_key = models.PositiveIntegerField(
        null=False,
        help_text=_(
            "Relative order this Institution is displayed in a UserProfile."),
        verbose_name=_("Display order"),
    )
Example #14
0
class StudyPermission(Permission, models.Model):
    """
    Access given for a *specific* study instance, rather than for object types provided
    by Django.
    """

    class Meta:
        abstract = True

    study = models.ForeignKey(
        "main.Study",
        help_text=_("Study this permission applies to."),
        on_delete=models.CASCADE,
        verbose_name=_("Study"),
    )
    permission_type = VarCharField(
        choices=Permission.TYPE_CHOICE,
        default=Permission.NONE,
        help_text=_("Type of permission."),
        verbose_name=_("Permission"),
    )

    def get_type_label(self):
        return dict(self.TYPE_CHOICE).get(self.permission_type, "?")

    def is_read(self):
        """
        Test if the permission grants read privileges.

        :returns: True if permission grants read access
        """
        return self.permission_type in self.CAN_VIEW

    def is_write(self):
        """
        Test if the permission grants write privileges.

        :returns: True if permission grants write access
        """
        return self.permission_type in self.CAN_EDIT
Example #15
0
File: models.py Project: JBEI/edd
class DefaultUnit(models.Model):
    class Meta:
        db_table = "default_unit"

    measurement_type = models.ForeignKey(
        edd_models.MeasurementType,
        on_delete=models.deletion.CASCADE,
        verbose_name=_("Measurement Type"),
    )
    unit = models.ForeignKey(edd_models.MeasurementUnit,
                             on_delete=models.deletion.CASCADE)
    protocol = models.ForeignKey(edd_models.Protocol,
                                 blank=True,
                                 null=True,
                                 on_delete=models.deletion.CASCADE)
    parser = VarCharField(blank=True, null=True)

    def to_json(self):
        return {
            "id": self.pk,
            "type_name": self.measurement_type.type_name,
            "unit_name": self.unit.unit_name,
        }
Example #16
0
class EDDObject(EDDMetadata, EDDSerialize):
    """A first-class EDD object, with update trail, comments, attachments."""
    class Meta:
        db_table = "edd_object"

    objects = EDDObjectManager()

    name = VarCharField(help_text=_("Name of this object."),
                        verbose_name=_("Name"))
    description = models.TextField(
        blank=True,
        help_text=_("Description of this object."),
        null=True,
        verbose_name=_("Description"),
    )
    active = models.BooleanField(
        default=True,
        help_text=_("Flag showing if this object is active and displayed."),
        verbose_name=_("Active"),
    )
    updates = models.ManyToManyField(
        Update,
        db_table="edd_object_update",
        help_text=_("List of Update objects logging changes to this object."),
        related_name="+",
        verbose_name=_("Updates"),
    )
    # these are used often enough we should save extra queries by including as fields
    created = models.ForeignKey(
        Update,
        editable=False,
        help_text=_("Update used to create this object."),
        on_delete=models.PROTECT,
        related_name="object_created",
        verbose_name=_("Created"),
    )
    updated = models.ForeignKey(
        Update,
        editable=False,
        help_text=_("Update used to last modify this object."),
        on_delete=models.PROTECT,
        related_name="object_updated",
        verbose_name=_("Last Modified"),
    )
    # linking together EDD instances will be easier later if we define UUIDs now
    uuid = models.UUIDField(
        editable=False,
        help_text=_("Unique identifier for this object."),
        unique=True,
        verbose_name=_("UUID"),
    )

    @property
    def mod_epoch(self):
        return arrow.get(self.updated.mod_time).int_timestamp

    @property
    def last_modified(self):
        return self.updated.format_timestamp()

    def was_modified(self):
        return self.updates.count() > 1

    @property
    def date_created(self):
        return self.created.format_timestamp()

    def get_attachment_count(self):
        if hasattr(self, "_file_count"):
            return self._file_count
        return self.files.count()

    @property
    def attachments(self):
        return self.files.all()

    @property
    def comment_list(self):
        return self.comments.order_by("created__mod_time").all()

    def get_comment_count(self):
        if hasattr(self, "_comment_count"):
            return self._comment_count
        return self.comments.count()

    @classmethod
    def metadata_type_frequencies(cls):
        return dict(
            # TODO: do this with Django model APIs instead of raw SQL
            MetadataType.objects.extra(
                select={
                    "count":
                    "SELECT COUNT(1) FROM edd_object o "
                    f"INNER JOIN {cls._meta.db_table} x ON o.id = x.object_ref_id "
                    "WHERE o.metadata ? metadata_type.id::varchar"
                }).values_list("id", "count"))

    def __str__(self):
        return self.name

    @classmethod
    def export_columns(cls, table_generator, instances=None):
        # define column for object ID
        table_generator.define_field_column(cls._meta.get_field("id"),
                                            heading=f"{cls.__name__} ID")
        # define column for object name
        table_generator.define_field_column(cls._meta.get_field("name"),
                                            heading=f"{cls.__name__} Name")

    def to_json(self, depth=0):
        return {
            "id": self.pk,
            "name": self.name,
            "description": self.description,
            "active": self.active,
            "meta": self.metadata,
            # Always include expanded created/updated objects instead of IDs
            "modified": self.updated.to_json(depth) if self.updated else None,
            "created": self.created.to_json(depth) if self.created else None,
        }

    def to_json_str(self, depth=0):
        """
        Used in overview.html. Serializing directly in the template creates
        strings like "u'description'" that Javascript can't parse.
        """
        json_dict = self.to_json(depth)
        return json.dumps(json_dict, ensure_ascii=False).encode("utf8")

    def user_can_read(self, user):
        return True

    def user_can_write(self, user):
        return user and user.is_superuser
Example #17
0
class Measurement(EDDMetadata, EDDSerialize):
    """A plot of data points for an (assay, measurement type) pair."""
    class Meta:
        db_table = "measurement"

    class Compartment:
        """
        Enumeration of localized compartments applying to the measurement.

        UNKNOWN = default; no specific localization
        INTRACELLULAR = measurement inside of a cell, in cytosol
        EXTRACELLULAR = measurement outside of a cell
        """

        UNKNOWN = "0"
        INTRACELLULAR = "1"
        EXTRACELLULAR = "2"
        namecode = namedtuple("namecode", ("name", "code"))
        names = {
            UNKNOWN: namecode(_("N/A"), _("")),
            INTRACELLULAR: namecode(_("Intracellular/Cytosol (Cy)"), _("IC")),
            EXTRACELLULAR: namecode(_("Extracellular"), _("EC")),
        }
        CHOICE = tuple((k, v.name) for k, v in names.items())

        @classmethod
        def to_json(cls):
            return {k: {"id": k, **v._asdict()} for k, v in cls.names.items()}

    class Format:
        """
        Enumeration of formats measurement values can take.

        SCALAR = single timepoint X value, single measurement Y value
            (one item array)
        VECTOR = single timepoint X value, vector measurement Y value
            (mass-distribution, index by labeled carbon count; interpret each
            value as ratio with sum of all values)
        HISTOGRAM_NAIVE = single timepoint X value, vector measurement Y value
            (bins with counts of population measured within bin value, bin
            size/range set via y_units)
        SIGMA = single timepoint X value, 3-item-list Y value (average,
            variance, sample size)
        RANGE = single timepoint X value, 3-item-list Y value (best, hi, lo)
        VECTOR_RANGE = single timepoint X value, 3n vector Y value
            (mass-distribution, n best values first, n hi values, n lo values,
            index by xn + labeled carbon count)
        PACKED = series of scalar values packed into a single pair of
            value vectors
        HISTOGRAM = timepoint plus n+1 X values, vector of n Y values per bin
        HISTOGRAM_STEP = timepoint, start, step X values, vector Y values
            per bin
        """

        SCALAR = "0"
        VECTOR = "1"
        HISTOGRAM_NAIVE = "2"
        SIGMA = "3"
        RANGE = "4"
        VECTOR_RANGE = "5"
        PACKED = "6"
        HISTOGRAM = "7"
        HISTOGRAM_STEP = "8"
        names = {
            SCALAR: _("scalar"),
            VECTOR: _("vector"),
            HISTOGRAM_NAIVE: _("histogram (deprecated)"),
            SIGMA: _("sigma"),
            RANGE: _("range"),
            VECTOR_RANGE: _("vector range"),
            PACKED: _("packed"),
            HISTOGRAM: _("histogram"),
            HISTOGRAM_STEP: _("stepped histogram"),
        }
        CHOICE = tuple(names.items())

    study = models.ForeignKey(
        Study,
        help_text=_("The Study containing this Measurement."),
        on_delete=models.CASCADE,
        verbose_name=_("Study"),
    )
    assay = models.ForeignKey(
        Assay,
        help_text=_("The Assay creating this Measurement."),
        on_delete=models.CASCADE,
        verbose_name=_("Assay"),
    )
    experimenter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        blank=True,
        help_text=
        _("EDD User that set up the experimental conditions of this Measurement."
          ),
        null=True,
        on_delete=models.PROTECT,
        related_name="measurement_experimenter_set",
        verbose_name=_("Experimenter"),
    )
    measurement_type = models.ForeignKey(
        MeasurementType,
        help_text=_("The type of item measured for this Measurement."),
        on_delete=models.PROTECT,
        verbose_name=_("Type"),
    )
    x_units = models.ForeignKey(
        MeasurementUnit,
        help_text=_("The units of the X-axis for this Measurement."),
        on_delete=models.PROTECT,
        related_name="+",
        verbose_name=_("X Units"),
    )
    y_units = models.ForeignKey(
        MeasurementUnit,
        help_text=_("The units of the Y-axis for this Measurement."),
        on_delete=models.PROTECT,
        related_name="+",
        verbose_name=_("Y Units"),
    )
    update_ref = models.ForeignKey(
        Update,
        help_text=_("The Update triggering the setting of this Measurement."),
        on_delete=models.PROTECT,
        verbose_name=_("Updated"),
    )
    active = models.BooleanField(
        default=True,
        help_text=
        _("Flag indicating this Measurement is active and should be displayed."
          ),
        verbose_name=_("Active"),
    )
    compartment = VarCharField(
        choices=Compartment.CHOICE,
        default=Compartment.UNKNOWN,
        help_text=_("Compartment of the cell for this Measurement."),
        verbose_name=_("Compartment"),
    )
    measurement_format = VarCharField(
        choices=Format.CHOICE,
        default=Format.SCALAR,
        help_text=_("Enumeration of value formats for this Measurement."),
        verbose_name=_("Format"),
    )

    @classmethod
    def export_columns(cls, table_generator, instances=None):
        table_generator.define_field_column(
            cls._meta.get_field("measurement_type"),
            lookup=lambda measure: measure.measurement_type.export_name(),
        )
        table_generator.define_field_column(
            cls._meta.get_field("measurement_type"),
            heading=_("Formal Type ID"),
            key="formal_id",
            lookup=measurement_formal_id,
        )
        table_generator.define_field_column(
            cls._meta.get_field("update_ref"),
            heading=_("Measurement Updated"),
            lookup=lambda measure: measure.update_ref.mod_time,
        )
        table_generator.define_field_column(
            cls._meta.get_field("x_units"),
            lookup=measurement_x_unit,
        )
        table_generator.define_field_column(
            cls._meta.get_field("y_units"),
            lookup=measurement_y_unit,
        )

    def to_json(self, depth=0):
        return {
            "id": self.pk,
            "assay": self.get_attr_depth("assay", depth),
            "type": self.get_attr_depth("measurement_type", depth),
            "comp": self.compartment,
            "format": self.measurement_format,
            # including points here is extremely inefficient
            # better to directly filter MeasurementValue and map to parent IDs later
            # "values": map(lambda p: p.to_json(), self.measurementvalue_set.all()),
            "x_units": self.x_units_id,
            "y_units": self.y_units_id,
            "meta": self.metadata,
        }

    def __str__(self):
        return f"Measurement[{self.assay_id}][{self.measurement_type}]"

    # may not be the best method name, if we ever want to support other
    # types of data as vectors in the future
    def is_carbon_ratio(self):
        return self.measurement_format == Measurement.Format.VECTOR

    def valid_data(self):
        """Data for which the y-value is defined (non-NULL, non-blank)."""
        mdata = list(self.data())
        return [md for md in mdata if md.is_defined()]

    def is_extracellular(self):
        return self.compartment == Measurement.Compartment.EXTRACELLULAR

    def data(self):
        """Return the data associated with this measurement."""
        return self.measurementvalue_set.all()

    @property
    def name(self):
        """alias for self.measurement_type.type_name"""
        return self.measurement_type.type_name

    @property
    def compartment_symbol(self):
        return Measurement.Compartment.short_names[int(self.compartment)]

    @property
    def full_name(self):
        """measurement compartment plus measurement_type.type_name"""
        lookup = dict(Measurement.Compartment.CHOICE)
        return (lookup.get(self.compartment) + " " + self.name).strip()

    # TODO also handle vectors
    def extract_data_xvalues(self, defined_only=False):
        qs = self.measurementvalue_set.order_by("x")
        if defined_only:
            qs = qs.exclude(Q(y=None) | Q(y__len=0))
        # first index unpacks single value from tuple
        # second index unpacks first value from X
        return [x[0][0] for x in qs.values_list("x")]

    # this shouldn't need to handle vectors
    def interpolate_at(self, x):
        if self.measurement_format != Measurement.Format.SCALAR:
            raise ValueError("Can only interpolate scalar values")
        from main.utilities import interpolate_at

        return interpolate_at(self.valid_data(), x)

    @property
    def y_axis_units_name(self):
        """
        Human-readable units for Y-axis. Not intended for repeated/bulk use,
        since it involves a foreign key lookup.
        """
        return self.y_units.unit_name

    def is_concentration_measurement(self):
        return self.y_axis_units_name in [
            "mg/L", "g/L", "mol/L", "mM", "uM", "Cmol/L"
        ]

    @classmethod
    def active_in(cls, *, study_id, protocol_id, assay_id=None):
        """
        Queries all active/enabled measurements matching criteria.
        """
        assay_filter = Q() if assay_id is None else Q(assay_id=assay_id)
        active = cls.objects.filter(
            assay_filter,
            active=True,
            assay__active=True,
            assay__line__active=True,
            assay__line__study_id=study_id,
            assay__protocol_id=protocol_id,
        )
        return active
Example #18
0
class MetadataType(models.Model, EDDSerialize):
    """Type information for arbitrary key-value data stored on EDDObject instances."""

    # defining values to use in the for_context field
    STUDY = "S"
    LINE = "L"
    ASSAY = "A"
    CONTEXT_SET = ((STUDY, _("Study")), (LINE, _("Line")), (ASSAY, _("Assay")))

    # pre-defined values that should always exist in the system
    _SYSTEM_TYPES = (
        # type_field metadata to map to Model object fields
        Metadata(
            for_context=ASSAY,
            input_type="textarea",
            type_field="description",
            type_i18n="main.models.Assay.description",
            type_name="Assay Description",
            uuid="4929a6ad-370c-48c6-941f-6cd154162315",
        ),
        Metadata(
            for_context=ASSAY,
            input_type="user",
            type_field="experimenter",
            type_i18n="main.models.Assay.experimenter",
            type_name="Assay Experimenter",
            uuid="15105bee-e9f1-4290-92b2-d7fdcb3ad68d",
        ),
        Metadata(
            for_context=ASSAY,
            input_type="string",
            type_field="name",
            type_i18n="main.models.Assay.name",
            type_name="Assay Name",
            uuid="33125862-66b2-4d22-8966-282eb7142a45",
        ),
        Metadata(
            for_context=LINE,
            input_type="carbon_source",
            type_field="carbon_source",
            type_i18n="main.models.Line.carbon_source",
            type_name="Carbon Source(s)",
            uuid="4ddaf92a-1623-4c30-aa61-4f7407acfacc",
        ),
        Metadata(
            for_context=LINE,
            input_type="checkbox",
            type_field="control",
            type_i18n="main.models.Line.control",
            type_name="Control",
            uuid="8aa26735-e184-4dcd-8dd1-830ec240f9e1",
        ),
        Metadata(
            for_context=LINE,
            input_type="user",
            type_field="contact",
            type_i18n="main.models.Line.contact",
            type_name="Line Contact",
            uuid="13672c8a-2a36-43ed-928f-7d63a1a4bd51",
        ),
        Metadata(
            for_context=LINE,
            input_type="textarea",
            type_field="description",
            type_i18n="main.models.Line.description",
            type_name="Line Description",
            uuid="5fe84549-9a97-47d2-a897-8c18dd8fd34a",
        ),
        Metadata(
            for_context=LINE,
            input_type="user",
            type_field="experimenter",
            type_i18n="main.models.Line.experimenter",
            type_name="Line Experimenter",
            uuid="974c3367-f0c5-461d-bd85-37c1a269d49e",
        ),
        Metadata(
            for_context=LINE,
            input_type="string",
            type_field="name",
            type_i18n="main.models.Line.name",
            type_name="Line Name",
            uuid="b388bcaa-d14b-4d7f-945e-a6fcb60142f2",
        ),
        Metadata(
            for_context=LINE,
            input_type="strain",
            type_field="strains",
            type_i18n="main.models.Line.strains",
            type_name="Strain(s)",
            uuid="292f1ca7-30de-4ba1-89cd-87d2f6291416",
        ),
        # "true" metadata, but directly referenced by code for specific purposes
        Metadata(
            default_value="--",
            for_context=LINE,
            input_type="media",
            type_i18n="main.models.Line.Media",
            type_name="Media",
            uuid="463546e4-a67e-4471-a278-9464e78dbc9d",
        ),
        Metadata(
            for_context=ASSAY,
            # TODO: consider making this: input_type="readonly"
            input_type="string",
            type_i18n="main.models.Assay.original",
            type_name="Original Name",
            uuid="5ef6500e-0f8b-4eef-a6bd-075bcb655caa",
        ),
        Metadata(
            for_context=LINE,
            input_type="replicate",
            type_i18n="main.models.Line.replicate",
            type_name="Replicate",
            uuid="71f5cd94-4dd4-45ca-a926-9f0717631799",
        ),
        Metadata(
            for_context=ASSAY,
            input_type="time",
            type_i18n="main.models.Assay.Time",
            type_name="Time",
            uuid="6629231d-4ef0-48e3-a21e-df8db6dfbb72",
        ),
    )
    _SYSTEM_DEF = {t.type_name: t for t in _SYSTEM_TYPES}
    SYSTEM = {t.type_name: t.uuid for t in _SYSTEM_TYPES}

    class Meta:
        db_table = "metadata_type"
        unique_together = (("type_name", "for_context"),)

    # optionally link several metadata types into a common group
    group = models.ForeignKey(
        MetadataGroup,
        blank=True,
        help_text=_("Group for this Metadata Type"),
        null=True,
        on_delete=models.PROTECT,
        verbose_name=_("Group"),
    )
    # a default label for the type; should normally use i18n lookup for display
    type_name = VarCharField(
        help_text=_("Name for Metadata Type"), verbose_name=_("Name")
    )
    # an i18n lookup for type label
    type_i18n = VarCharField(
        blank=True,
        help_text=_("i18n key used for naming this Metadata Type."),
        null=True,
        verbose_name=_("i18n Key"),
    )
    # field to store metadata, or None if stored in metadata
    type_field = VarCharField(
        blank=True,
        default=None,
        help_text=_(
            "Model field where metadata is stored; blank stores in metadata dictionary."
        ),
        null=True,
        verbose_name=_("Field Name"),
    )
    # type of the input on front-end; support checkboxes, autocompletes, etc
    # blank/null falls back to plain text input field
    input_type = VarCharField(
        blank=True,
        help_text=_("Type of input fields for values of this Metadata Type."),
        null=True,
        verbose_name=_("Input Type"),
    )
    # a default value to use if the field is left blank
    default_value = VarCharField(
        blank=True,
        help_text=_("Default value for this Metadata Type."),
        verbose_name=_("Default Value"),
    )
    # label used to prefix values
    prefix = VarCharField(
        blank=True,
        help_text=_("Prefix text appearing before values of this Metadata Type."),
        verbose_name=_("Prefix"),
    )
    # label used to postfix values (e.g. unit specifier)
    postfix = VarCharField(
        blank=True,
        help_text=_("Postfix text appearing after values of this Metadata Type."),
        verbose_name=_("Postfix"),
    )
    # target object for metadata
    for_context = VarCharField(
        choices=CONTEXT_SET,
        help_text=_("Type of EDD Object this Metadata Type may be added to."),
        verbose_name=_("Context"),
    )
    # linking together EDD instances will be easier later if we define UUIDs now
    uuid = models.UUIDField(
        editable=False,
        help_text=_("Unique identifier for this Metadata Type."),
        unique=True,
        verbose_name=_("UUID"),
    )

    @classmethod
    def all_types_on_instances(cls, instances):
        # grab all the keys on each instance metadata
        all_ids = [
            set(o.metadata.keys()) for o in instances if isinstance(o, EDDMetadata)
        ]
        # reduce all into a set to get only unique ids
        ids = set().union(*all_ids)
        return MetadataType.objects.filter(pk__in=ids).order_by(
            Func(F("type_name"), function="LOWER")
        )

    @classmethod
    def system(cls, name):
        """Load a pre-defined system-wide MetadataType."""
        typedef = cls._SYSTEM_DEF.get(name, None)
        if typedef is None:
            raise cls.DoesNotExist
        fields = {f.name for f in dataclasses.fields(Metadata)}
        defaults = {k: v for k, v in typedef.__dict__.items() if k in fields and v}
        meta, created = cls.objects.get_or_create(uuid=typedef.uuid, defaults=defaults)
        return meta

    def decode_value(self, value):
        """
        Default MetadataType class reflects back the passed value loaded from
        JSON. Subclasses may try to modify the value to convert to arbitrary
        Python values instead of a JSON-compatible dict.
        """
        return value

    def encode_value(self, value):
        """
        Default MetadataType class reflects back the passed value to send to
        JSON. Subclasses may try to modify the value to serialize arbitrary
        Python values to a JSON-compatible value.
        """
        return value

    def for_line(self):
        return self.for_context == self.LINE

    def for_assay(self):
        return self.for_context == self.ASSAY

    def for_study(self):
        return self.for_context == self.STUDY

    def __str__(self):
        return self.type_name

    def to_json(self, depth=0):
        return {
            "id": self.pk,
            "name": self.type_name,
            "i18n": self.type_i18n,
            "input_type": self.input_type,
            "prefix": self.prefix,
            "postfix": self.postfix,
            "default": self.default_value,
            "context": self.for_context,
        }
Example #19
0
class ProteinIdentifier(MeasurementType):
    """Defines additional metadata on proteomic measurement type."""
    class Meta:
        db_table = "protein_identifier"

    # protein names use:
    #   type_name = "human-readable" name; e.g. AATM_RABIT
    #   accession_code = accession code ID portion; e.g. P12345
    #   accession_id = "full" accession ID if available; e.g. sp|P12345|AATM_RABIT
    #       if "full" version unavailable, repeat the accession_code
    accession_id = VarCharField(
        blank=True,
        help_text=_("Accession ID for protein characterized in e.g. UniProt."),
        null=True,
        verbose_name=_("Accession ID"),
    )
    accession_code = VarCharField(
        blank=True,
        help_text=_("Required portion of Accession ID for easier lookup."),
        null=True,
        verbose_name=_("Accession Code"),
    )
    length = models.IntegerField(blank=True,
                                 help_text=_("sequence length"),
                                 null=True,
                                 verbose_name=_("Length"))
    mass = models.DecimalField(
        blank=True,
        decimal_places=5,
        help_text=_("of unprocessed protein, in Daltons"),
        max_digits=16,
        null=True,
        verbose_name=_("Mass"),
    )

    # TODO find how this can also match JGI accession IDs
    accession_pattern = re.compile(
        # optional identifier for SwissProt or TrEMBL
        r"(?:[a-z]{2}\|)?"
        # the ID
        r"([OPQ][0-9][A-Z0-9]{3}[0-9]|[A-NR-Z][0-9](?:[A-Z][A-Z0-9]{2}[0-9]){1,2})"
        # optional name
        r"(?:\|(\w+))?")

    def export_name(self):
        if self.accession_id:
            return self.accession_id
        return self.type_name

    def to_solr_json(self):
        """
        Convert the MeasurementType model to a dict structure formatted for Solr JSON.
        """
        return dict(super().to_solr_json(), **{
            "p_length": self.length,
            "p_mass": self.mass
        })

    def update_from_uniprot(self):
        match = self.accession_pattern.match(self.accession_id)
        if match:
            uniprot_id = match.group(1)
            for name, value in self._load_uniprot_values(uniprot_id):
                setattr(self, name, value)
            if not self.provisional:
                self.save()

    @classmethod
    def _get_or_create_from_uniprot(cls, uniprot_id, accession_id):
        try:
            protein = cls.objects.get(accession_code=uniprot_id)
        except cls.DoesNotExist:
            url = cls._uniprot_url(uniprot_id)
            protein = cls.objects.create(
                accession_code=uniprot_id,
                accession_id=accession_id,
                type_source=Datasource.objects.create(name="UniProt", url=url),
            )
        return protein

    @classmethod
    def _load_uniprot(cls, uniprot_id, accession_id):
        try:
            protein = cls._get_or_create_from_uniprot(uniprot_id, accession_id)
            lookup_protein_in_uniprot.delay(protein.id)
            return protein
        except Exception:
            logger.exception(f"Failed to create from UniProt {uniprot_id}")
            raise ValidationError(
                _u("Could not create Protein from {uniprot_id}").format(
                    uniprot_id=uniprot_id))

    @classmethod
    def _load_uniprot_values(cls, uniprot_id):
        url = cls._uniprot_url(uniprot_id)
        values = {}
        # define some RDF predicate terms
        mass_predicate = URIRef("http://purl.uniprot.org/core/mass")
        sequence_predicate = URIRef("http://purl.uniprot.org/core/sequence")
        value_predicate = URIRef(
            "http://www.w3.org/1999/02/22-rdf-syntax-ns#value")
        # build the RDF graph
        try:
            graph = Graph()
            graph.parse(url)
            # find top-level references
            subject = URIRef(f"http://purl.uniprot.org/uniprot/{uniprot_id}")
            isoform = graph.value(subject, sequence_predicate)
            # find values of interest
            values.update(
                type_name=cls._uniprot_name(graph, subject, uniprot_id))
            sequence = graph.value(isoform, value_predicate)
            if sequence:
                values.update(length=len(sequence.value))
            mass = graph.value(isoform, mass_predicate)
            if mass:
                values.update(mass=mass.value)
            values.update(provisional=False)
        except Exception:
            logger.exception(f"Failed to read UniProt: {uniprot_id}")
            values.update(provisional=True)
        return values

    @classmethod
    def _uniprot_name(cls, graph, subject, uniprot_id):
        """
        Parses the RDF for name using ordered preferences: recommendedName, then submittedName,
        then mnemonic, then uniprot_id.
        """
        fullname_predicate = URIRef("http://purl.uniprot.org/core/fullName")
        mnemonic_predicate = URIRef("http://purl.uniprot.org/core/mnemonic")
        recname_predicate = URIRef(
            "http://purl.uniprot.org/core/recommendedName")
        subname_predicate = URIRef(
            "http://purl.uniprot.org/core/submittedName")
        names = [
            # get the fullName value of the recommendedName
            graph.value(graph.value(subject, recname_predicate),
                        fullname_predicate),
            # get the fullName value of the submittedName
            graph.value(graph.value(subject, subname_predicate),
                        fullname_predicate),
            # get the literal value of the mnemonic
            getattr(graph.value(subject, mnemonic_predicate), "value", None),
        ]
        # fallback to uniprot_id if all above are None
        return next((name for name in names if name is not None), uniprot_id)

    @classmethod
    def _uniprot_url(cls, uniprot_id):
        return f"http://www.uniprot.org/uniprot/{uniprot_id}.rdf"

    @classmethod
    def _load_ice(cls, link):
        part = link.strain.part
        datasource = Datasource.objects.create(name="Part Registry",
                                               url=link.strain.registry_url)
        protein = cls.objects.create(
            type_name=link.strain.name,
            type_source=datasource,
            accession_id=part.part_id,
        )
        link.protein = protein
        link.save()
        return protein

    @classmethod
    def load_or_create(cls, protein_name, user):
        # extract Uniprot accession data from the measurement name, if present
        accession_match = cls.accession_pattern.match(protein_name)
        proteins = cls.objects.none()
        if accession_match:
            accession_code = accession_match.group(1)
            proteins = cls.objects.filter(accession_code=accession_code)
        else:
            proteins = cls.objects.filter(accession_code=protein_name)

        # force query to LIMIT 2, anything more than one is treated same
        proteins = proteins[:2]

        if len(proteins) > 1:
            # fail if protein couldn't be uniquely matched
            raise ValidationError(
                _u('More than one match was found for protein name "{type_name}".'
                   ).format(type_name=protein_name))
        elif len(proteins) == 0:
            # try to create a new protein
            link = ProteinStrainLink()
            if accession_match:
                # if it looks like a UniProt ID, look up in UniProt
                accession_code = accession_match.group(1)
                return cls._load_uniprot(accession_code, protein_name)
            elif link.check_ice(user.email, protein_name):
                # if it is found in ICE, create based on ICE info
                return cls._load_ice(link)
            elif getattr(settings, "REQUIRE_UNIPROT_ACCESSION_IDS", True):
                raise ValidationError(
                    _u('Protein name "{type_name}" is not a valid UniProt accession id.'
                       ).format(type_name=protein_name))
            logger.info(f"Creating a new ProteinIdentifier for {protein_name}")
            # not requiring accession ID or ICE entry; just create protein with arbitrary name
            datasource = Datasource.objects.create(name=user.username,
                                                   url=user.email)
            return cls.objects.create(
                type_name=protein_name,
                provisional=True,
                accession_code=protein_name,
                accession_id=protein_name,
                type_source=datasource,
            )
        return proteins[0]

    @classmethod
    def match_accession_id(cls, text):
        """
        Tests whether the input text matches the pattern of a Uniprot accession id,
        and if so, extracts & returns the required identifier portion of the text,
        less optional prefix/suffix allowed by the pattern.

        :param text: the text to match
        :return: the Uniprot identifier if the input text matched the accession id pattern,
            or the entire input string if not
        """
        match = cls.accession_pattern.match(text)
        if match:
            return match.group(1)
        return text

    def __str__(self):
        return self.type_name

    def save(self, *args, **kwargs):
        # force PROTEINID group
        self.type_group = MeasurementType.Group.PROTEINID
        super().save(*args, **kwargs)
Example #20
0
class MeasurementType(EDDSerialize, models.Model):
    """
    Defines the type of measurement being made. A generic measurement only
    has name and short name; if the type is a metabolite, the metabolite
    attribute will contain additional metabolite info.
    """
    class Meta:
        db_table = "measurement_type"

    class Group:
        """
        Note that when a new group type is added here, code will need to be
        updated elsewhere, including the Javascript/Typescript front end.
        Look for the string 'MeasurementGroupCode' in comments.
        """

        GENERIC = "_"
        METABOLITE = "m"
        GENEID = "g"
        PROTEINID = "p"
        PHOSPHOR = "h"
        GROUP_CHOICE = (
            (GENERIC, _("Generic")),
            (METABOLITE, _("Metabolite")),
            (GENEID, _("Gene Identifier")),
            (PROTEINID, _("Protein Identifier")),
            (PHOSPHOR, _("Phosphor")),
        )

    type_name = VarCharField(
        help_text=_("Name of this Measurement Type."),
        verbose_name=_("Measurement Type"),
    )
    short_name = VarCharField(
        blank=True,
        help_text=_("(DEPRECATED) Short name used in SBML output."),
        null=True,
        verbose_name=_("Short Name"),
    )
    type_group = VarCharField(
        choices=Group.GROUP_CHOICE,
        default=Group.GENERIC,
        help_text=_("Class of data for this Measurement Type."),
        verbose_name=_("Type Group"),
    )
    type_source = models.ForeignKey(
        Datasource,
        blank=True,
        help_text=_(
            "Datasource used for characterizing this Measurement Type."),
        null=True,
        on_delete=models.PROTECT,
        verbose_name=_("Datasource"),
    )
    provisional = models.BooleanField(
        default=False,
        help_text=
        _("Flag indicating if the type is pending lookup in external Datasource"
          ),
        verbose_name=_("Provisional"),
    )
    # linking together EDD instances will be easier later if we define UUIDs now
    uuid = models.UUIDField(
        editable=False,
        help_text=_("Unique ID for this Measurement Type."),
        unique=True,
        verbose_name=_("UUID"),
    )
    alt_names = ArrayField(
        VarCharField(),
        blank=True,
        default=list,
        help_text=_("Alternate names for this Measurement Type."),
        verbose_name=_("Synonyms"),
    )

    def save(self, *args, **kwargs):
        if self.uuid is None:
            self.uuid = uuid4()
        super().save(*args, **kwargs)

    def to_solr_value(self):
        return f"{self.pk}@{self.type_name}"

    def to_solr_json(self):
        """
        Convert the MeasurementType model to a dict structure formatted for Solr JSON.
        """
        source_name = None
        # Check if this is coming from a child MeasurementType, and ref the base type
        mtype = getattr(self, "measurementtype_ptr", None)
        # check for annotated source attribute on self and base type
        if hasattr(self, "_source_name"):
            source_name = self._source_name
        elif mtype and hasattr(mtype, "_source_name"):
            source_name = mtype._source_name
        elif self.type_source:
            source_name = self.type_source.name
        return {
            "id": self.id,
            "uuid": self.uuid,
            "name": self.type_name,
            "family": self.type_group,
            # use the annotated attr if present, otherwise must make a new query
            "source": source_name,
        }

    def to_json(self, depth=0):
        payload = {
            "id": self.pk,
            "uuid": self.uuid,
            "name": self.type_name,
            "family": self.type_group,
        }
        # optionally add CID or Accession if from annotated query
        if (cid := getattr(self, "cid", None)) is not None:
            payload["cid"] = cid
        elif (accession := getattr(self, "accession", None)) is not None:
            payload["accession"] = accession
Example #21
0
class WorklistColumn(models.Model):
    """Defines metadata defaults and layout."""
    class Meta:
        constraints = (models.constraints.UniqueConstraint(
            condition=models.Q(ordering__isnull=False),
            fields=("ordering", "template"),
            name="unique_column_ordering",
        ), )
        db_table = "worklist_column"

    template = models.ForeignKey(
        WorklistTemplate,
        help_text=_("Parent Worklist Template for this column."),
        on_delete=models.CASCADE,
        verbose_name=_("Template"),
    )
    # if meta_type is None, treat default_value as format string
    meta_type = models.ForeignKey(
        metadata.MetadataType,
        blank=True,
        help_text=_("Type of Metadata in this column."),
        null=True,
        on_delete=models.PROTECT,
        verbose_name=_("Metadata Type"),
    )
    # if None, default to meta_type.type_name or ''
    heading = VarCharField(
        blank=True,
        help_text=_("Column header text."),
        null=True,
        verbose_name=_("Heading"),
    )
    # potentially override the default value in templates?
    default_value = VarCharField(
        blank=True,
        help_text=_("Default value for this column."),
        null=True,
        verbose_name=_("Default Value"),
    )
    # text to display in UI explaining how to modify column
    help_text = models.TextField(
        blank=True,
        help_text=_(
            "UI text to display explaining how to modify this column."),
        null=True,
        verbose_name=_("Help Text"),
    )
    # allow ordering of metadata
    ordering = models.IntegerField(
        blank=True,
        help_text=_("Order this column will appear in worklist export."),
        null=True,
        verbose_name=_("Ordering"),
    )

    def get_default(self):
        if self.default_value:
            return self.default_value
        elif self.meta_type:
            return self.meta_type.default_value
        return ""

    def get_format_dict(self, instance, *args, **kwargs):
        """
        Build dict used in format string for columns that use it. This
        implementation re-uses EDDObject.to_json(), in a flattened format.
        """
        fmt_dict = flatten_json(instance.to_json(depth=1) if instance else {})
        # add in: date
        # TODO: pass in tz based on user profile?
        fmt_dict.update(today=arrow.now().format("YYYYMMDD"))
        fmt_dict.update(**kwargs)
        return fmt_dict

    def __str__(self):
        if self.heading:
            return self.heading
        return str(self.meta_type)
Example #22
0
class Migration(migrations.Migration):

    initial = True

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ("main", "0001_edd_2_7"),
    ]

    operations = [
        migrations.CreateModel(
            name="StudyLog",
            fields=[
                (
                    "id",
                    models.UUIDField(
                        default=uuid.uuid4,
                        editable=False,
                        primary_key=True,
                        serialize=False,
                        unique=True,
                    ),
                ),
                (
                    "detail",
                    models.JSONField(
                        blank=True,
                        decoder=JSONDecoder,
                        default=dict,
                        encoder=JSONEncoder,
                        editable=False,
                        help_text=
                        "JSON structure with extra details specific to the event.",
                        verbose_name="Details",
                    ),
                ),
                (
                    "event",
                    VarCharField(
                        choices=[
                            ("STUDY_CREATED", "Study Created"),
                            ("STUDY_DESCRIBED", "Study Described"),
                            ("STUDY_EXPORTED", "Study Exported"),
                            ("STUDY_IMPORTED", "Study Imported"),
                            ("STUDY_PERMISSION", "Study Permission Changed"),
                            ("STUDY_VIEWED", "Study Viewed"),
                            ("STUDY_WORKLIST", "Study Worklist"),
                        ],
                        editable=False,
                        help_text="Type of logged metric event.",
                        verbose_name="Event",
                    ),
                ),
                (
                    "timestamp",
                    models.DateTimeField(
                        auto_now_add=True,
                        help_text="Timestamp of the logged metric event.",
                        verbose_name="Timestamp",
                    ),
                ),
                (
                    "study",
                    models.ForeignKey(
                        blank=True,
                        editable=False,
                        help_text=
                        "The Study associated with the logged metric event.",
                        null=True,
                        on_delete=models.deletion.SET_NULL,
                        related_name="metric_log",
                        to="main.study",
                        verbose_name="Study",
                    ),
                ),
                (
                    "user",
                    models.ForeignKey(
                        editable=False,
                        help_text=
                        "The user triggering the logged metric event.",
                        null=True,
                        on_delete=models.deletion.SET_NULL,
                        to=settings.AUTH_USER_MODEL,
                        verbose_name="User",
                    ),
                ),
            ],
            options={
                "verbose_name": "Study Log",
                "verbose_name_plural": "Study Logs"
            },
        ),
    ]
Example #23
0
class Attachment(models.Model):
    """
    File uploads attached to an EDDObject; include MIME, file name,
    and description.
    """
    class Meta:
        db_table = "attachment"

    object_ref = models.ForeignKey("EDDObject",
                                   on_delete=models.CASCADE,
                                   related_name="files")
    file = FileField(
        help_text=_("Path to file data."),
        max_length=None,
        upload_to="%Y/%m/%d",
        verbose_name=_("File Path"),
    )
    filename = VarCharField(help_text=_("Name of attachment file."),
                            verbose_name=_("File Name"))
    created = models.ForeignKey(
        Update,
        help_text=_("Update used to create the attachment."),
        on_delete=models.PROTECT,
        verbose_name=_("Created"),
    )
    description = models.TextField(
        blank=True,
        help_text=_("Description of attachment file contents."),
        null=False,
        verbose_name=_("Description"),
    )
    mime_type = VarCharField(
        blank=True,
        help_text=_("MIME ContentType of the attachment."),
        null=True,
        verbose_name=_("MIME"),
    )
    file_size = models.IntegerField(
        default=0,
        help_text=_("Total byte size of the attachment."),
        verbose_name=_("Size"),
    )

    extensions_to_icons = defaultdict(
        lambda: "icon-generic.png",
        {
            ".zip": "icon-zip.png",
            ".gzip": "icon-zip.png",
            ".bzip": "icon-zip.png",
            ".gz": "icon-zip.png",
            ".dmg": "icon-zip.png",
            ".rar": "icon-zip.png",
            ".ico": "icon-image.gif",
            ".gif": "icon-image.gif",
            ".jpg": "icon-image.gif",
            ".jpeg": "icon-image.gif",
            ".png": "icon-image.gif",
            ".tif": "icon-image.gif",
            ".tiff": "icon-image.gif",
            ".psd": "icon-image.gif",
            ".svg": "icon-image.gif",
            ".mov": "icon-video.png",
            ".avi": "icon-video.png",
            ".mkv": "icon-video.png",
            ".txt": "icon-text.png",
            ".rtf": "icon-text.png",
            ".wri": "icon-text.png",
            ".htm": "icon-text.png",
            ".html": "icon-text.png",
            ".pdf": "icon-pdf.gif",
            ".ps": "icon-pdf.gif",
            ".key": "icon-keynote.gif",
            ".mdb": "icon-mdb.png",
            ".doc": "icon-word.png",
            ".ppt": "icon-ppt.gif",
            ".xls": "icon-excel.png",
            ".xlsx": "icon-excel.png",
        },
    )

    def __str__(self):
        return self.filename

    @property
    def user_initials(self):
        return self.created.initials

    @property
    def icon(self):
        base, ext = os.path.splitext(self.filename)
        return self.extensions_to_icons[ext]

    def user_can_delete(self, user):
        """
        Verify that a user has the appropriate permissions to delete
        an attachment.
        """
        return self.object_ref.user_can_write(user)

    def user_can_read(self, user):
        """
        Verify that a user has the appropriate permissions to see (that is,
        download) an attachment.
        """
        return self.object_ref.user_can_read(user)
Example #24
0
class Campaign(edd_models.core.SlugMixin, models.Model):
    """A grouping of studies, with a broad goal; multiple cycles of DBTL."""

    # linking together EDD instances will be easier later if we define UUIDs now
    uuid = models.UUIDField(
        editable=False,
        help_text=_("Unique identifier for this Campaign."),
        unique=True,
        verbose_name=_("UUID"),
    )
    name = VarCharField(help_text=_("Name of this Campaign."),
                        verbose_name=_("Name"))
    description = models.TextField(
        blank=True,
        help_text=_("Description of this Campaign."),
        null=True,
        verbose_name=_("Description"),
    )
    # create a slug for a more human-readable URL
    slug = models.SlugField(
        help_text=_("Slug text used in links to this Campaign."),
        null=True,
        unique=True,
        verbose_name=_("Slug"),
    )
    updates = models.ManyToManyField(
        edd_models.Update,
        help_text=_(
            "List of Update objects logging changes to this Campaign."),
        related_name="+",
        verbose_name=_("Updates"),
    )
    # these are used often enough we should save extra queries by including as fields
    created = models.ForeignKey(
        edd_models.Update,
        editable=False,
        help_text=_("Update used to create this Campaign."),
        on_delete=models.PROTECT,
        related_name="+",
        verbose_name=_("Created"),
    )
    updated = models.ForeignKey(
        edd_models.Update,
        editable=False,
        help_text=_("Update used to last modify this Campaign."),
        on_delete=models.PROTECT,
        related_name="+",
        verbose_name=_("Last Modified"),
    )
    studies = models.ManyToManyField(
        edd_models.Study,
        blank=True,
        help_text=_("Studies that are part of this Campaign."),
        through="CampaignMembership",
        verbose_name=_("Studies"),
    )

    @staticmethod
    def filter_for(user, access=CampaignPermission.CAN_VIEW):
        """
        Similar to main.models.Study.access_filter(); however, this will only build
        a filter for Campaign objects. These permissions should not be relied upon
        to cascade to Study objects and children linked by Campaign objects. This
        call should be used in a queryset .filter() used with a .distinct();
        otherwise, if a user has multiple permission paths to a Campaign, multiple
        results may be returned.
        """
        if isinstance(access, str):
            access = (access, )
        q = Q(everyonepermission__campaign_permission__in=access)
        if is_real_user(user):
            q |= Q(
                userpermission__user=user,
                userpermission__campaign_permission__in=access,
            ) | Q(
                grouppermission__group__user=user,
                grouppermission__campaign_permission__in=access,
            )
        return q

    def check_permissions(self, link_type, operation, user):
        return (is_real_user(user) and user.is_superuser) or any(
            p.is_allowed(link_type, operation)
            for p in self.get_permissions(user))

    def get_all_permissions(self):
        return chain(
            self.userpermission_set.all(),
            self.grouppermission_set.all(),
            self.everyonepermission_set.all(),
        )

    def get_permissions(self, user):
        if is_real_user(user):
            return chain(
                self.userpermission_set.filter(user=user),
                self.grouppermission_set.filter(group__user=user),
                self.everyonepermission_set.all(),
            )
        return self.everyonepermission_set.all()

    def user_can_read(self, user):
        is_super = is_real_user(user) and user.is_superuser
        has_permission = any(p.is_read() for p in self.get_permissions(user))
        return is_super or has_permission

    def user_can_write(self, user):
        is_super = is_real_user(user) and user.is_superuser
        has_permission = any(p.is_write() for p in self.get_permissions(user))
        return is_super or has_permission
Example #25
0
class Migration(migrations.Migration):

    dependencies = [
        ("main", "0003_remove_carbonsource"),
    ]

    operations = [
        migrations.CreateModel(
            name="protocolio",
            fields=[
                (
                    "id",
                    models.AutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                (
                    "name",
                    VarCharField(help_text="Name of this Protocol.",
                                 verbose_name="Name"),
                ),
                (
                    "uuid",
                    models.UUIDField(
                        editable=False,
                        help_text="Unique identifier for this Protocol.",
                        unique=True,
                        verbose_name="UUID",
                    ),
                ),
                (
                    "external_url",
                    models.URLField(
                        blank=True,
                        help_text=
                        "The URL in external service (e.g. protocols.io)",
                        null=True,
                        unique=True,
                    ),
                ),
                (
                    "active",
                    models.BooleanField(
                        default=True,
                        help_text=
                        "Flag showing if this Protocol is active and displayed.",
                        verbose_name="Active",
                    ),
                ),
                (
                    "destructive",
                    models.BooleanField(
                        default=False,
                        help_text=
                        "Flag showing if this Protocol consumes a sample.",
                        verbose_name="Destructive",
                    ),
                ),
                (
                    "sbml_category",
                    VarCharField(
                        blank=True,
                        choices=[
                            ("OD", "Optical Density"),
                            ("HPLC", "HPLC"),
                            ("LCMS", "LCMS"),
                            ("RAMOS", "RAMOS"),
                            ("TPOMICS", "Transcriptomics / Proteomics"),
                        ],
                        default=None,
                        help_text="SBML category for this Protocol.",
                        null=True,
                        verbose_name="SBML Category",
                    ),
                ),
                (
                    "created",
                    models.ForeignKey(
                        editable=False,
                        help_text="Update used to create this Protocol.",
                        on_delete=PROTECT,
                        related_name="protocol_created",
                        to="main.update",
                        verbose_name="Created",
                    ),
                ),
                (
                    "updated",
                    models.ForeignKey(
                        editable=False,
                        help_text="Update used to last modify this Protocol.",
                        on_delete=PROTECT,
                        related_name="protocol_updated",
                        to="main.update",
                        verbose_name="Last Modified",
                    ),
                ),
            ],
            options={"db_table": "main_protocol"},
        ),
        migrations.AddIndex(
            model_name="protocolio",
            index=models.Index(fields=["active", "name"],
                               name="main_protoc_active_f664e6_idx"),
        ),
        migrations.AddIndex(
            model_name="protocolio",
            index=models.Index(fields=["sbml_category"],
                               name="main_protoc_sbml_ca_7d7196_idx"),
        ),
        migrations.RunPython(code=copy_to_protocolio,
                             reverse_code=migrations.RunPython.noop),
        migrations.AddField(
            model_name="assay",
            name="protocolio",
            field=models.ForeignKey(
                blank=True,
                null=True,
                on_delete=PROTECT,
                to="main.protocolio",
            ),
        ),
        migrations.AddField(
            model_name="worklisttemplate",
            name="protocolio",
            field=models.ForeignKey(
                blank=True,
                null=True,
                on_delete=PROTECT,
                to="main.protocolio",
            ),
        ),
        migrations.RunPython(code=switch_protocol,
                             reverse_code=migrations.RunPython.noop),
        migrations.RemoveField(model_name="assay", name="protocol"),
        migrations.RemoveField(model_name="worklisttemplate", name="protocol"),
        migrations.DeleteModel(name="protocol"),
        migrations.RenameField(model_name="assay",
                               old_name="protocolio",
                               new_name="protocol"),
        migrations.RenameField(
            model_name="worklisttemplate",
            old_name="protocolio",
            new_name="protocol",
        ),
        migrations.RenameModel(old_name="protocolio", new_name="protocol"),
        migrations.AlterField(
            model_name="assay",
            name="protocol",
            field=models.ForeignKey(
                help_text="The Protocol used to create this Assay.",
                on_delete=PROTECT,
                to="main.protocol",
                verbose_name="Protocol",
            ),
        ),
        migrations.AlterField(
            model_name="line",
            name="protocols",
            field=models.ManyToManyField(
                help_text="Protocol(s) used to Assay this Line.",
                through="main.assay",
                to="main.protocol",
                verbose_name="Protocol(s)",
            ),
        ),
        migrations.AlterField(
            model_name="protocol",
            name="destructive",
            field=models.BooleanField(
                default=False,
                help_text="Flag showing if this protocol consumes a sample.",
                verbose_name="Destructive",
            ),
        ),
        migrations.AlterField(
            model_name="study",
            name="protocols",
            field=models.ManyToManyField(
                blank=True,
                db_table="study_protocol",
                help_text="Protocols planned for use in this Study.",
                to="main.protocol",
                verbose_name="Protocols",
            ),
        ),
        migrations.AlterField(
            model_name="worklisttemplate",
            name="protocol",
            field=models.ForeignKey(
                help_text="Default protocol for this Template.",
                on_delete=PROTECT,
                to="main.protocol",
                verbose_name="Protocol",
            ),
        ),
        # these fields aren't used and are causing Django to generate infinite migrations
        migrations.RemoveField(
            model_name="line",
            name="protocols",
        ),
        migrations.RemoveField(
            model_name="study",
            name="protocols",
        ),
    ]
Example #26
0
class CampaignPermission(BasePermission, models.Model):
    """Permissions specific to a Campaign."""

    ADD = "add"
    REMOVE = "remove"
    LEVEL_OVERRIDES = {
        BasePermission.NONE: (),
        BasePermission.READ: (BasePermission.NONE, ),
        BasePermission.WRITE: (BasePermission.NONE, BasePermission.READ),
    }
    LINKS = set()

    class Meta:
        abstract = True

    campaign = models.ForeignKey(
        "Campaign",
        help_text=_("Campaign this permission applies to."),
        on_delete=models.CASCADE,
        verbose_name=_("Campaign"),
    )
    study_permission = VarCharField(
        choices=BasePermission.TYPE_CHOICE,
        default=BasePermission.NONE,
        help_text=_(
            "Type of permission applied to Studies linked to Campaign."),
        verbose_name=_("Study Permission"),
    )
    campaign_permission = VarCharField(
        choices=BasePermission.TYPE_CHOICE,
        default=BasePermission.NONE,
        help_text=_("Permission for read/write on the Campaign itself."),
        verbose_name=_("Campaign Permission"),
    )
    link_permissions = ArrayField(
        models.TextField(),
        default=list,
        help_text=_("Additional permissions applying to this Campaign."),
        verbose_name=_("Additional Flags"),
    )

    @classmethod
    def convert_link_type(cls, link_type, operation):
        return f"{link_type.__module__}.{link_type.__qualname__}:{operation}"

    @classmethod
    def register_link(cls, link_type, operation):
        """
        Adds the ability to create permissions for arbitrary types and operations
        tied to a Campaign. e.g. if code elsewhere adds a Widget type linked to
        Campaigns, and would like to limit the users that may do the Florf
        operation on those Widgets:

            class Widget(models.Model):
                def user_can_florf(self, user):
                    return any(
                        p.is_allowed(Widget, "florf")
                        for p in self.campaign.get_permissions(user)
                    )

            CampaignPermission.register_link(Widget, "florf")
        """
        cls.LINKS.add(cls.convert_link_type(link_type, operation))

    @classmethod
    def unregister_link(cls, link_type, operation):
        """
        Removes a type and operation registration from those available to be
        managed via CampaignPermission restrictions.
        """
        cls.LINKS.remove(cls.convert_link_type(link_type, operation))

    def __getitem__(self, key):
        # only return boolean for valid keys in self.LINKS
        if key in self.LINKS:
            return key in self.link_permissions
        # templates do getitem lookups before attribute lookups, so fallback to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        if key not in self.LINKS:
            raise ValueError(
                f"{key} is not registered as a Campaign permission")
        if value:
            # avoid adding duplicates
            if key not in self.link_permissions:
                self.link_permissions.append(key)
        else:
            # remove if present
            try:
                self.link_permissions.remove(key)
            except ValueError:
                logging.info(f"Removing permission {key} but it was not set")

    def get_permission_overrides(self):
        return self.LEVEL_OVERRIDES.get(self.study_permission, [])

    def get_type_label(self):
        return dict(self.TYPE_CHOICE).get(self.campaign_permission, "?")

    def is_allowed(self, link_type, operation):
        link = self.convert_link_type(link_type, operation)
        return link in self.link_permissions

    def is_read(self):
        """
        Test if the permission grants read privileges.

        :returns: True if permission grants read access
        """
        return self.campaign_permission in self.CAN_VIEW

    def is_write(self):
        """
        Test if the permission grants write privileges.

        :returns: True if permission grants write access
        """
        return self.campaign_permission in self.CAN_EDIT

    def set_allowed(self, link_type, operation, allow=True):
        """
        Change the state of this permission for adding linked objects.

        :param link_type: the class of object to modify adding link permissions
        :param allow: boolean state for permission; True allows adding link, False
            dis-allows adding link. (Default True)
        """
        link = self.convert_link_type(link_type, operation)
        self[link] = allow
Example #27
0
class Protocol(EDDObject):
    """A defined method of examining a Line."""
    class Meta:
        db_table = "protocol"

    CATEGORY_NONE = "NA"
    CATEGORY_OD = "OD"
    CATEGORY_HPLC = "HPLC"
    CATEGORY_LCMS = "LCMS"
    CATEGORY_RAMOS = "RAMOS"
    CATEGORY_TPOMICS = "TPOMICS"
    CATEGORY_CHOICE = (
        (CATEGORY_NONE, _("None")),
        (CATEGORY_OD, _("Optical Density")),
        (CATEGORY_HPLC, _("HPLC")),
        (CATEGORY_LCMS, _("LCMS")),
        (CATEGORY_RAMOS, _("RAMOS")),
        (CATEGORY_TPOMICS, _("Transcriptomics / Proteomics")),
    )

    object_ref = models.OneToOneField(EDDObject,
                                      on_delete=models.CASCADE,
                                      parent_link=True,
                                      related_name="+")
    owned_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        help_text=_("Owner / maintainer of this Protocol"),
        on_delete=models.PROTECT,
        related_name="protocol_set",
        verbose_name=_("Owner"),
    )
    variant_of = models.ForeignKey(
        "self",
        blank=True,
        help_text=_(
            "Link to another original Protocol used as basis for this Protocol."
        ),
        null=True,
        on_delete=models.PROTECT,
        related_name="derived_set",
        verbose_name=_("Variant of Protocol"),
    )
    default_units = models.ForeignKey(
        "MeasurementUnit",
        blank=True,
        help_text=_("Default units for values measured with this Protocol."),
        null=True,
        on_delete=models.SET_NULL,
        related_name="protocol_set",
        verbose_name=_("Default Units"),
    )
    categorization = VarCharField(
        choices=CATEGORY_CHOICE,
        default=CATEGORY_NONE,
        help_text=_("SBML category for this Protocol."),
        verbose_name=_("SBML Category"),
    )

    def creator(self):
        return self.created.mod_by

    def owner(self):
        return self.owned_by

    def last_modified(self):
        return self.updated.mod_time

    def to_solr_value(self):
        return f"{self.pk}@{self.name}"

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if self.name in ["", None]:
            raise ValueError("Protocol name required.")
        p = Protocol.objects.filter(name=self.name)
        if (self.id is not None and p.count() > 1) or (self.id is None
                                                       and p.count() > 0):
            raise ValueError(
                f"There is already a protocol named '{self.name}'.")
        return super().save(*args, **kwargs)
Example #28
0
class Metabolite(MeasurementType):
    """
    Defines additional metadata on a metabolite measurement type;
    charge, carbon count, molar mass, molecular formula, SMILES, PubChem CID.
    """
    class Meta:
        db_table = "metabolite"

    charge = models.IntegerField(help_text=_("The charge of this molecule."),
                                 verbose_name=_("Charge"))
    carbon_count = models.IntegerField(
        help_text=_("Count of carbons present in this molecule."),
        verbose_name=_("Carbon Count"),
    )
    molar_mass = models.DecimalField(
        decimal_places=5,
        help_text=_("Molar mass of this molecule."),
        max_digits=16,
        verbose_name=_("Molar Mass"),
    )
    molecular_formula = models.TextField(
        help_text=_("Formula string defining this molecule."),
        verbose_name=_("Formula"))
    smiles = VarCharField(
        blank=True,
        help_text=_("SMILES string defining molecular structure."),
        null=True,
        verbose_name=_("SMILES"),
    )
    pubchem_cid = models.IntegerField(
        blank=True,
        help_text=_("Unique PubChem identifier"),
        null=True,
        unique=True,
        verbose_name=_("PubChem CID"),
    )
    id_map = ArrayField(
        VarCharField(),
        default=list,
        help_text=_(
            "List of identifiers mapping to external chemical datasets."),
        verbose_name=_("External IDs"),
    )
    tags = ArrayField(
        VarCharField(),
        default=list,
        help_text=_("List of tags for classifying this molecule."),
        verbose_name=_("Tags"),
    )

    carbon_pattern = re.compile(r"C(?![a-z])(\d*)")
    pubchem_pattern = re.compile(r"(?i)cid:\s*(\d+)(?::(.*))?")

    def __str__(self):
        return self.type_name

    def is_metabolite(self):
        return True

    def to_json(self, depth=0):
        """Export a serializable dictionary."""
        return dict(
            super().to_json(),
            **{
                "formula": self.molecular_formula,
                "molar": float(self.molar_mass),
                "carbons": self.carbon_count,
                "pubchem": self.pubchem_cid,
                "smiles": self.smiles,
            },
        )

    def to_solr_json(self):
        """Convert the MeasurementType model to a dict structure formatted for Solr JSON."""
        return dict(
            super().to_solr_json(),
            **{
                "m_charge": self.charge,
                "m_carbons": self.carbon_count,
                "m_mass": self.molar_mass,
                "m_formula": self.molecular_formula,
                "m_tags": list(self.tags),
            },
        )

    def save(self, *args, **kwargs):
        if self.carbon_count is None:
            self.carbon_count = self.extract_carbon_count()
        # force METABOLITE group
        self.type_group = MeasurementType.Group.METABOLITE
        super().save(*args, **kwargs)

    def extract_carbon_count(self):
        count = 0
        for match in self.carbon_pattern.finditer(self.molecular_formula):
            c = match.group(1)
            count = count + (int(c) if c else 1)
        return count

    def _load_pubchem(self, pubchem_cid):
        base_url = (
            f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{pubchem_cid}"
        )
        try:
            self.pubchem_cid = pubchem_cid
            if self._load_pubchem_name(base_url) and self._load_pubchem_props(
                    base_url):
                self.type_source = Datasource.objects.create(name="PubChem",
                                                             url=base_url)
                self.carbon_count = self.extract_carbon_count()
                self.provisional = False
                self.save()
                return True
            logger.warn(f"Skipped saving PubChem info for {pubchem_cid}")
        except Exception:
            logger.exception(
                f"Failed processing PubChem info for {pubchem_cid}")
        return False

    def _load_pubchem_name(self, base_url):
        # the default properties listing does not give common names, synonyms list does
        try:
            response = requests.get(f"{base_url}/synonyms/JSON")
            # payload is nested in this weird envelope
            names = response.json(
            )["InformationList"]["Information"][0]["Synonym"]
            # set the first synonym
            self.type_name = next(iter(names))
            return True
        except Exception:
            logger.exception(
                f"Failed loading names from PubChem for {self.pubchem_cid}")
        return False

    def _load_pubchem_props(self, base_url):
        # can list out only specific properties needed in URL
        props = "MolecularFormula,MolecularWeight,Charge,CanonicalSMILES"
        try:
            response = requests.get(f"{base_url}/property/{props}/JSON")
            # payload is nested in this weird envelope
            table = response.json()["PropertyTable"]["Properties"][0]
            # set the properties found
            self.charge = table.get("Charge", 0)
            self.molecular_formula = table.get("MolecularFormula", "")
            self.molar_mass = table.get("MolecularWeight", 1)
            self.smiles = table.get("CanonicalSMILES", "")
            return True
        except Exception:
            logger.exception(
                f"Failed loading properties from Pubchem for {self.pubchem_cid}"
            )
        return False

    @classmethod
    def load_or_create(cls, pubchem_cid):
        match = cls.pubchem_pattern.match(pubchem_cid)
        if match:
            cid = match.group(1)
            label = match.group(2)
            # try to find existing Metabolite record
            metabolite, created = cls.objects.get_or_create(
                pubchem_cid=cid,
                defaults={
                    "carbon_count": 0,
                    "charge": 0,
                    "molar_mass": 1,
                    "molecular_formula": "",
                    "provisional": True,
                    "type_group": MeasurementType.Group.METABOLITE,
                    "type_name": label or "Unknown Metabolite",
                },
            )
            if created:
                transaction.on_commit(
                    lambda: metabolite_load_pubchem.delay(metabolite.pk))
            return metabolite
        raise ValidationError(
            _u('Metabolite lookup failed: {pubchem} must match pattern "cid:0000"'
               ).format(pubchem=pubchem_cid))
Example #29
0
class Migration(migrations.Migration):

    replaces = [
        ("profile", "0001_initial"),
        ("profile", "0002_auto_20150729_1523"),
        ("profile", "0003_usertask"),
        ("profile", "0004_userprofile_preferences"),
        ("profile", "0005_remove_hstore"),
        ("profile", "0006_remove_usertask"),
        ("profile", "0007_use_varchar"),
        ("profile", "0008_add_approval_flag"),
        ("profile", "0009_add_institution_order"),
    ]

    initial = True

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ("auth", "0011_update_proxy_permissions"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            fields=[
                (
                    "id",
                    models.AutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("password",
                 models.CharField(max_length=128, verbose_name="password")),
                (
                    "last_login",
                    models.DateTimeField(blank=True,
                                         null=True,
                                         verbose_name="last login"),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text=
                        "Designates that this user has all permissions "
                        "without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique":
                            "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. "
                        "Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[UnicodeUsernameValidator()],
                        verbose_name="username",
                    ),
                ),
                (
                    "first_name",
                    models.CharField(blank=True,
                                     max_length=150,
                                     verbose_name="first name"),
                ),
                (
                    "last_name",
                    models.CharField(blank=True,
                                     max_length=150,
                                     verbose_name="last name"),
                ),
                (
                    "email",
                    models.EmailField(blank=True,
                                      max_length=254,
                                      verbose_name="email address"),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text=
                        "Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text=
                        "Designates whether this user should be treated as active. "
                        "Unselect this instead of deleting accounts.",
                        verbose_name="active",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(default=timezone.now,
                                         verbose_name="date joined"),
                ),
                (
                    "groups",
                    models.ManyToManyField(
                        blank=True,
                        help_text="The groups this user belongs to. "
                        "A user will get all permissions granted to each of their groups.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.Group",
                        verbose_name="groups",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.Permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={"db_table": "auth_user"},
            managers=[("profiles", ProfileUserManager()),
                      ("objects", UserManager())],
        ),
        migrations.CreateModel(
            name="Institution",
            fields=[
                (
                    "id",
                    models.AutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("institution_name", VarCharField()),
                ("description", models.TextField(blank=True, null=True)),
            ],
            options={"db_table": "profile_institution"},
        ),
        migrations.CreateModel(
            name="InstitutionID",
            fields=[
                (
                    "id",
                    models.AutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("identifier", VarCharField(blank=True, null=True)),
                (
                    "sort_key",
                    models.PositiveIntegerField(
                        help_text=
                        "Relative order this Institution is displayed in a UserProfile.",
                        verbose_name="Display order",
                    ),
                ),
                (
                    "institution",
                    models.ForeignKey(
                        on_delete=models.deletion.CASCADE,
                        to="profile.Institution",
                    ),
                ),
            ],
            options={"db_table": "profile_institution_user"},
        ),
        migrations.CreateModel(
            name="UserProfile",
            fields=[
                (
                    "id",
                    models.AutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("initials", VarCharField(blank=True, null=True)),
                ("description", models.TextField(blank=True, null=True)),
                (
                    "preferences",
                    models.JSONField(blank=True, default=dict),
                ),
                (
                    "approved",
                    models.BooleanField(
                        default=False,
                        help_text=
                        "Flag showing if this account has been approved for login.",
                        verbose_name="Approved",
                    ),
                ),
                (
                    "institutions",
                    models.ManyToManyField(through="profile.InstitutionID",
                                           to="profile.Institution"),
                ),
                (
                    "user",
                    models.OneToOneField(
                        on_delete=models.deletion.CASCADE,
                        to=settings.AUTH_USER_MODEL,
                    ),
                ),
            ],
            options={"db_table": "profile_user"},
        ),
        migrations.AddField(
            model_name="institutionid",
            name="profile",
            field=models.ForeignKey(on_delete=models.deletion.CASCADE,
                                    to="profile.UserProfile"),
        ),
        migrations.AddConstraint(
            model_name="institutionid",
            constraint=models.UniqueConstraint(
                fields=("profile", "sort_key"),
                name="profile_institution_ordering_idx"),
        ),
    ]
Example #30
0
class StudyLog(models.Model):
    """Recorded entry for Study metrics captured by EDD."""
    class Event(models.TextChoices):
        CREATED = "STUDY_CREATED", _("Study Created")
        DESCRIBED = "STUDY_DESCRIBED", _("Study Described")
        EXPORTED = "STUDY_EXPORTED", _("Study Exported")
        IMPORTED = "STUDY_IMPORTED", _("Study Imported")
        PERMISSION = "STUDY_PERMISSION", _("Study Permission Changed")
        VIEWED = "STUDY_VIEWED", _("Study Viewed")
        WORKLIST = "STUDY_WORKLIST", _("Study Worklist")

    class Meta:
        verbose_name = _("Study Log")
        verbose_name_plural = _("Study Logs")

    # should never need to reference this, but need a primary key
    _id = models.UUIDField(
        default=uuid.uuid4,
        editable=False,
        name="id",
        primary_key=True,
        unique=True,
    )
    # store extra values that only exist on certain events here
    detail = models.JSONField(
        blank=True,
        decoder=JSONDecoder,
        editable=False,
        encoder=JSONEncoder,
        help_text=_(
            "JSON structure with extra details specific to the event."),
        default=dict,
        verbose_name=_("Details"),
    )
    event = VarCharField(
        blank=False,
        choices=Event.choices,
        editable=False,
        help_text=_("Type of logged metric event."),
        verbose_name=_("Event"),
    )
    study = models.ForeignKey(
        edd_models.Study,
        blank=True,
        editable=False,
        help_text=_("The Study associated with the logged metric event."),
        on_delete=models.SET_NULL,
        null=True,
        related_name="metric_log",
        verbose_name=_("Study"),
    )
    timestamp = models.DateTimeField(
        auto_now_add=True,
        editable=False,
        help_text=_("Timestamp of the logged metric event."),
        verbose_name=_("Timestamp"),
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        editable=False,
        help_text=_("The user triggering the logged metric event."),
        null=True,
        on_delete=models.SET_NULL,
        verbose_name=_("User"),
    )

    @classmethod
    def lookup_study(cls, study_id):
        try:
            return edd_models.Study.objects.get(id=study_id)
        except edd_models.Study.DoesNotExist:
            return None