Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pydatalab/schemas/sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
"$ref": "#/definitions/Constituent"
}
},
"synthesis_products": {
"title": "Synthesis Products",
"default": [],
"type": "array",
"items": {
"$ref": "#/definitions/Constituent"
}
},
"synthesis_description": {
"title": "Synthesis Description",
"type": "string"
Expand Down
8 changes: 8 additions & 0 deletions pydatalab/schemas/startingmaterial.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
"$ref": "#/definitions/Constituent"
}
},
"synthesis_products": {
"title": "Synthesis Products",
"default": [],
"type": "array",
"items": {
"$ref": "#/definitions/Constituent"
}
},
"synthesis_description": {
"title": "Synthesis Description",
"type": "string"
Expand Down
1 change: 1 addition & 0 deletions pydatalab/src/pydatalab/models/samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ class Sample(Item, HasSynthesisInfo):

chemform: str | None = Field(example=["Na3P", "LiNiO2@C"])
"""A string representation of the chemical formula or composition associated with this sample."""

62 changes: 15 additions & 47 deletions pydatalab/src/pydatalab/models/traits.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pydantic import BaseModel, Field, root_validator

from pydatalab.models.people import Person
from pydatalab.models.utils import Constituent, InlineSubstance, PyObjectId
from pydatalab.models.utils import Constituent, PyObjectId


class HasOwner(BaseModel):
Expand Down Expand Up @@ -86,57 +86,25 @@ class HasSynthesisInfo(BaseModel):
synthesis_constituents: list[Constituent] = Field([])
"""A list of references to constituent materials giving the amount and relevant inlined details of consituent items."""

synthesis_products: list[Constituent] = Field([])
"""A list of references to constituent materials giving the amount and relevant inlined details of relevant sythesis products."""

synthesis_description: str | None = None
"""Free-text details of the procedure applied to synthesise the sample"""

@root_validator
def add_missing_synthesis_relationships(cls, values):
"""Add any missing sample synthesis constituents to parent relationships"""
from pydatalab.models.relationships import RelationshipType, TypedRelationship

constituents_set = set()
if values.get("synthesis_constituents") is not None:
existing_parent_relationship_ids = set()
if values.get("relationships") is not None:
existing_parent_relationship_ids = {
relationship.refcode or relationship.item_id
for relationship in values["relationships"]
if relationship.relation == RelationshipType.PARENT
}
else:
values["relationships"] = []

for constituent in values.get("synthesis_constituents", []):
# If this is an inline relationship, just skip it
if isinstance(constituent.item, InlineSubstance):
continue

constituent_id = constituent.item.refcode or constituent.item.item_id

if constituent_id not in existing_parent_relationship_ids:
relationship = TypedRelationship(
relation=RelationshipType.PARENT,
refcode=constituent.item.refcode,
item_id=constituent.item.item_id,
type=constituent.item.type,
description="Is a constituent of",
)
values["relationships"].append(relationship)

# Accumulate all constituent IDs in a set to filter those that have been deleted
constituents_set.add(constituent_id)

# Finally, filter out any parent relationships with item that were removed
# from the synthesis constituents
values["relationships"] = [
rel
for rel in values["relationships"]
if not (
(rel.refcode or rel.item_id) not in constituents_set
and rel.relation == RelationshipType.PARENT
and rel.type in ("samples", "starting_materials")
def add_self_product(cls, values):
if not values.get("synthesis_products"):
values["synthesis_products"].append(
Constituent(
quantity=None,
item={
"type": values["type"],
"refcode": values["refcode"],
"item_id": values["item_id"],
},
)
)
]

return values

Expand Down
2 changes: 1 addition & 1 deletion pydatalab/src/pydatalab/routes/v0_1/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ def get_item_data(
)

# Must be exported to JSON first to apply the custom pydantic JSON encoders
return_dict = json.loads(doc.json(exclude_unset=True))
return_dict = json.loads(doc.json())

if item_id is None:
item_id = return_dict["item_id"]
Expand Down
6 changes: 3 additions & 3 deletions webapp/src/components/CompactConstituentTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export default {
},
props: {
modelValue: {
type: String,
default: "",
type: Array,
default: () => [],
},
typesToQuery: {
type: Array,
Expand All @@ -132,7 +132,7 @@ export default {
},
computed: {
newSelectIsShown() {
return this.constituents.length == 0 || this.addNewConstituentIsActive;
return this.addNewConstituentIsActive;
},
constituents: {
get() {
Expand Down
54 changes: 51 additions & 3 deletions webapp/src/components/SynthesisInformation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,31 @@
class="content-container"
:style="{ 'max-height': contentMaxHeight }"
>
<span id="synthesis-reactants-label" class="subheading mt-2 pb-2 ml-2">
<label for="synthesis-reactants-table">Reactants, reagents, inputs</label>
</span>
<div class="card component-card">
<div class="card-body pt-2 pb-0 mb-0 pl-5">
<CompactConstituentTable
id="synthesis-table"
id="synthesis-reactants-table"
v-model="constituents"
:types-to-query="['samples', 'starting_materials']"
/>
</div>
</div>
<span id="synthesis-procedure-label" class="subheading ml-2">Procedure</span>
<span id="synthesis-products-label" class="subheading mt-2 pb-2 ml-2"
><label for="synthesis-products-table">Products</label></span
>
<div class="card component-card">
<div class="card-body pt-2 pb-0 mb-0 pl-5">
<CompactConstituentTable
id="synthesis-products-table"
v-model="products"
:types-to-query="['samples', 'starting_materials']"
/>
</div>
</div>
<span id="synthesis-procedure-label" class="subheading ml-2"><label>Procedure</label></span>
<TinyMceInline
v-model="SynthesisDescription"
aria-labelledby="synthesis-procedure-label"
Expand Down Expand Up @@ -59,6 +74,7 @@ export default {
},
computed: {
constituents: createComputedSetterForItemField("synthesis_constituents"),
products: createComputedSetterForItemField("synthesis_products"),
SynthesisDescription: createComputedSetterForItemField("synthesis_description"),
},
watch: {
Expand All @@ -69,6 +85,12 @@ export default {
},
deep: true,
},
products: {
handler() {
this.$store.commit("setItemSaved", { item_id: this.item_id, isSaved: false });
},
deep: true,
},
SynthesisDescription: {
handler() {
this.$store.commit("setItemSaved", { item_id: this.item_id, isSaved: false });
Expand All @@ -80,6 +102,7 @@ export default {
// Auto-collapsed when initialised empty
this.isExpanded =
(this.constituents && this.constituents.length > 0) ||
(this.products && this.products.length > 0) ||
(this.SynthesisDescription && this.SynthesisDescription.trim() !== "");
// If expanded set height to none, otherwise set to 0px
if (this.isExpanded) {
Expand Down Expand Up @@ -122,6 +145,24 @@ export default {
this.selectShown.push(false);
this.isExpanded = true;
},
addProduct(selectedItem) {
this.products.push({
item: selectedItem,
quantity: null,
unit: "g",
});
this.selectedNewProduct = null;
this.selectShown.push(false);
this.isExpanded = true;
},
turnOnRowProductSelect(index) {
this.selectProductShown[index] = true;
this.selectedChangedProduct = this.products[index].item;
this.$nextTick(function () {
// unfortunately this seems to be the "official" way to focus on the select element:
this.$refs[`select${index}`].$refs.selectComponent.$refs.search.focus();
});
},
turnOnRowSelect(index) {
this.selectShown[index] = true;
this.selectedChangedConstituent = this.constituents[index].item;
Expand All @@ -134,6 +175,14 @@ export default {
this.constituents[index].item = selectedItem;
this.selectShown[index] = false;
},
swapProduct(selectedItem, index) {
this.products[index].item = selectedItem;
this.selectShown[index] = false;
},
removeProduct(index) {
this.products.splice(index, 1);
this.selectShown.splice(index, 1);
},
removeConstituent(index) {
this.constituents.splice(index, 1);
this.selectShown.splice(index, 1);
Expand Down Expand Up @@ -201,7 +250,6 @@ export default {
font-size: small;
font-weight: 600;
text-transform: uppercase;
margin-bottom: 0px;
}

.content-container {
Expand Down
Loading