diff --git a/envipath/api.py b/envipath/api.py index 4e810b17..26983512 100644 --- a/envipath/api.py +++ b/envipath/api.py @@ -1,7 +1,14 @@ from epdb.api import router as epdb_app_router +from epdb.legacy_api import router as epdb_legacy_app_router from ninja import NinjaAPI api = NinjaAPI() -api.add_router("/", epdb_app_router) +from ninja import NinjaAPI +api_v1 = NinjaAPI(title="API V1 Docs", urls_namespace="api-v1") +api_legacy = NinjaAPI(title="Legacy API Docs", urls_namespace="api-legacy") + +# Add routers +api_v1.add_router("/", epdb_app_router) +api_legacy.add_router("/", epdb_legacy_app_router) diff --git a/envipath/urls.py b/envipath/urls.py index 81757e48..10eacec9 100644 --- a/envipath/urls.py +++ b/envipath/urls.py @@ -17,11 +17,12 @@ Including another URLconf from django.contrib import admin from django.urls import include, path -from .api import api +from .api import api_v1, api_legacy urlpatterns = [ path("", include("epdb.urls")), path("", include("migration.urls")), path("admin/", admin.site.urls), - path("api/", api.urls), + path("api/v1/", api_v1.urls), + path("api/legacy/", api_legacy.urls), ] diff --git a/epdb/legacy_api.py b/epdb/legacy_api.py new file mode 100644 index 00000000..4c4dae1c --- /dev/null +++ b/epdb/legacy_api.py @@ -0,0 +1,739 @@ +from typing import List, Dict, Optional, Any + +from django.contrib.auth import get_user_model +from django.http import HttpResponse +from ninja import Router, Schema, Field, Form + +from utilities.chem import FormatConverter +from .logic import PackageManager +from .models import Compound, CompoundStructure, Package, User, UserPackagePermission, Rule, Reaction, Scenario, Pathway + + +def _anonymous_or_real(request): + if request.user.is_authenticated and not request.user.is_anonymous: + return request.user + return get_user_model().objects.get(username='anonymous') + + +# router = Router(auth=SessionAuth()) +router = Router() + + +class Error(Schema): + message: str + + +class SimpleObject(Schema): + id: str = Field(None, alias="url") + name: str = Field(None, alias="name") + reviewStatus: bool = Field(None, alias="package.reviewed") + + +################ +# Login/Logout # +################ +class SimpleUser(Schema): + id: str = Field(None, alias="url") + identifier: str = 'user' + name: str = Field(None, alias='username') + email: str = Field(None, alias='email') + + +@router.post("/", response={200: SimpleUser, 403: Error}) +def login(request, loginusername: Form[str], loginpassword: Form[str], hiddenMethod: Form[str]): + from django.contrib.auth import authenticate + from django.contrib.auth import login + email = User.objects.get(username=loginusername).email + user = authenticate(username=email, password=loginpassword) + if user: + login(request, user) + return user + else: + return 403, {'message': 'Invalid username or password'} + + +class SimpleGroup(Schema): + id: str + identifier: str = 'group' + name: str + + +########### +# Package # +########### +class SimplePackage(SimpleObject): + identifier: str = 'package' + reviewStatus: bool = Field(None, alias="reviewed") + + +class PackageSchema(Schema): + description: str = Field(None, alias="description") + id: str = Field(None, alias="url") + links: List[Dict[str, List[str | int]]] = Field([], alias="links") + name: str = Field(None, alias="name") + primaryGroup: Optional[SimpleGroup] = None + readers: List[Dict[str, str]] = Field([], alias="readers") + reviewComment: str = Field(None, alias="review_comment") + reviewStatus: str = Field(None, alias="review_status") + writers: List[Dict[str, str]] = Field([], alias="writers") + + @staticmethod + def resolve_links(obj: Package): + return [ + { + 'Pathways': [ + f'{obj.url}/pathway', obj.pathways.count() + ] + }, { + 'Rules': [ + f'{obj.url}/rule', obj.rules.count() + ] + }, { + 'Compounds': [ + f'{obj.url}/compound', obj.compounds.count() + ] + }, { + 'Reactions': [ + f'{obj.url}/reaction', obj.reactions.count() + ] + }, { + 'Relative Reasoning': [ + f'{obj.url}/relative-reasoning', obj.models.count() + ] + }, { + 'Scenarios': [ + f'{obj.url}/scenario', obj.scenarios.count() + ] + } + ] + + @staticmethod + def resolve_readers(obj: Package): + users = User.objects.filter( + id__in=UserPackagePermission.objects.filter( + package=obj, + permission=UserPackagePermission.READ[0] + ).values_list('user', flat=True) + ).distinct() + + return [{u.id: u.name} for u in users] + + @staticmethod + def resolve_writers(obj: Package): + users = User.objects.filter( + id__in=UserPackagePermission.objects.filter( + package=obj, + permission=UserPackagePermission.WRITE[0] + ).values_list('user', flat=True) + ).distinct() + + return [{u.id: u.name} for u in users] + + @staticmethod + def resolve_review_comment(obj): + return "" + + @staticmethod + def resolve_review_status(obj): + return 'reviewed' if obj.reviewed else 'unreviewed' + + +class PackageWrapper(Schema): + package: List['PackageSchema'] + + +@router.get("/package", response={200: PackageWrapper, 403: Error}) +def get_packages(request): + return {'package': PackageManager.get_all_readable_packages(request.user, include_reviewed=True)} + + +@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema, 403: Error}) +def get_package(request, package_uuid): + try: + return PackageManager.get_package_by_id(request.user, package_uuid) + except ValueError: + return 403, { + 'message': f'Getting Package with id {package_uuid} failed due to insufficient rights!'} + + +################################ +# Compound / CompoundStructure # +################################ +class SimpleCompound(SimpleObject): + identifier: str = 'compound' + + +class CompoundPathwayScenario(Schema): + scenarioId: str + scenarioName: str + scenarioType: str + + +class CompoundSchema(Schema): + aliases: List[str] = Field([], alias="aliases") + description: str = Field(None, alias="description") + externalReferences: Dict[str, List[str]] = Field(None, alias="external_references") + id: str = Field(None, alias="url") + halflifes: List[Dict[str, str]] = Field([], alias="halflifes") + identifier: str = 'compound' + imageSize: int = 600 + name: str = Field(None, alias="name") + pathwayScenarios: List[CompoundPathwayScenario] = Field([], alias="pathway_scenarios") + pathways: List['SimplePathway'] = Field([], alias="related_pathways") + pubchemCompoundReferences: List[str] = Field([], alias="pubchem_compound_references") + reactions: List['SimpleReaction'] = Field([], alias="related_reactions") + reviewStatus: str = Field(False, alias="review_status") + scenarios: List['SimpleScenario'] = Field([], alias="scenarios") + structures: List['CompoundStructureSchema'] = [] + + @staticmethod + def resolve_review_status(obj: CompoundStructure): + return 'reviewed' if obj.package.reviewed else 'unreviewed' + + @staticmethod + def resolve_external_references(obj: Compound): + # TODO + return {} + + @staticmethod + def resolve_structures(obj: Compound): + return CompoundStructure.objects.filter(compound=obj) + + @staticmethod + def resolve_halflifes(obj: Compound): + return [] + + @staticmethod + def resolve_pubchem_compound_references(obj: Compound): + return [] + + @staticmethod + def resolve_pathway_scenarios(obj: Compound): + return [ + { + 'scenarioId': 'https://envipath.org/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/cd8350cd-4249-4111-ba9f-4e2209338501', + 'scenarioName': 'Fritz, R. & Brauner, A. (1989) - (00004)', + 'scenarioType': 'Soil' + } + ] + + +class CompoundWrapper(Schema): + compound: List['SimpleCompound'] + + +class SimpleCompoundStructure(SimpleObject): + identifier: str = 'structure' + reviewStatus: bool = Field(None, alias="compound.package.reviewed") + + +class CompoundStructureSchema(Schema): + InChI: str = Field(None, alias="inchi") + aliases: List[str] = Field([], alias="aliases") + canonicalSmiles: str = Field(None, alias="canonical_smiles") + charge: int = Field(None, alias="charge") + description: str = Field(None, alias="description") + externalReferences: Dict[str, List[str]] = Field(None, alias="external_references") + formula: str = Field(None, alias="formula") + halflifes: List[Dict[str, str]] = Field([], alias="halflifes") + id: str = Field(None, alias="url") + identifier: str = 'structure' + imageSize: int = 600 + inchikey: str = Field(None, alias="inchikey") + isDefaultStructure: bool = Field(None, alias="is_default_structure") + mass: float = Field(None, alias="mass") + name: str = Field(None, alias="name") + pathways: List['SimplePathway'] = Field([], alias="related_pathways") + pubchemCompoundReferences: List[str] = Field([], alias="pubchem_compound_references") + reactions: List['SimpleReaction'] = Field([], alias="related_reactions") + reviewStatus: str = Field(None, alias="review_status") + scenarios: List['SimpleScenario'] = Field([], alias="scenarios") + smiles: str = Field(None, alias="smiles") + + @staticmethod + def resolve_review_status(obj: CompoundStructure): + return 'reviewed' if obj.compound.package.reviewed else 'unreviewed' + + @staticmethod + def resolve_inchi(obj: CompoundStructure): + return FormatConverter.InChI(obj.smiles) + + @staticmethod + def resolve_charge(obj: CompoundStructure): + print(obj.smiles) + print(FormatConverter.charge(obj.smiles)) + return FormatConverter.charge(obj.smiles) + + @staticmethod + def resolve_formula(obj: CompoundStructure): + return FormatConverter.formula(obj.smiles) + + @staticmethod + def resolve_mass(obj: CompoundStructure): + return FormatConverter.mass(obj.smiles) + + @staticmethod + def resolve_external_references(obj: CompoundStructure): + # TODO + return {} + + @staticmethod + def resolve_halflifes(obj: CompoundStructure): + return [] + + @staticmethod + def resolve_pubchem_compound_references(obj: CompoundStructure): + return [] + + @staticmethod + def resolve_pathway_scenarios(obj: CompoundStructure): + return [ + { + 'scenarioId': 'https://envipath.org/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/cd8350cd-4249-4111-ba9f-4e2209338501', + 'scenarioName': 'Fritz, R. & Brauner, A. (1989) - (00004)', + 'scenarioType': 'Soil' + } + ] + + +class CompoundStructureWrapper(Schema): + structure: List['SimpleCompoundStructure'] + + +@router.get("/compound", response={200: CompoundWrapper, 403: Error}) +def get_compounds(request): + qs = Compound.objects.none() + for p in PackageManager.get_reviewed_packages(): + qs |= Compound.objects.filter(package=p) + return {'compound': qs} + + +@router.get("/package/{uuid:package_uuid}/compound", response={200: CompoundWrapper, 403: Error}) +def get_package_compounds(request, package_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return {'compound': Compound.objects.filter(package=p).prefetch_related('package')} + except ValueError: + return 403, { + 'message': f'Getting Compounds for Package with id {package_uuid} failed due to insufficient rights!'} + + +@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}", response={200: CompoundSchema, 403: Error}) +def get_package_compound(request, package_uuid, compound_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return Compound.objects.get(package=p, uuid=compound_uuid) + except ValueError: + return 403, { + 'message': f'Getting Compound with id {compound_uuid} failed due to insufficient rights!'} + + +@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure", + response={200: CompoundStructureWrapper, 403: Error}) +def get_package_compound_structures(request, package_uuid, compound_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return {'structure': Compound.objects.get(package=p, uuid=compound_uuid).structures.all()} + except ValueError: + return 403, { + 'message': f'Getting CompoundStructures for Compound with id {compound_uuid} failed due to insufficient rights!'} + + +@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}", + response={200: CompoundStructureSchema, 403: Error}) +def get_package_compound_structure(request, package_uuid, compound_uuid, structure_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return CompoundStructure.objects.get(uuid=structure_uuid, + compound=Compound.objects.get(package=p, uuid=compound_uuid)) + except ValueError: + return 403, { + 'message': f'Getting CompoundStructure with id {structure_uuid} failed due to insufficient rights!'} + + +######### +# Rules # +######### +class SimpleRule(SimpleObject): + identifier: str = 'rule' + + @staticmethod + def resolve_url(obj: Rule): + return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-') + + +class RuleWrapper(Schema): + rule: List['SimpleRule'] + + +class SimpleRuleSchema(Schema): + aliases: List[str] = Field([], alias="aliases") + description: str = Field(None, alias="description") + ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers") + engine: str = 'ambit' + id: str = Field(None, alias="url") + identifier: str = Field(None, alias="identifier") + isCompositeRule: bool = False + name: str = Field(None, alias="name") + pathways: List['SimplePathway'] = Field([], alias="related_pathways") + productFilterSmarts: str = Field("", alias="product_filter_smarts") + productSmarts: str = Field(None, alias="products_smarts") + reactantFilterSmarts: str = Field("", alias="reactant_filter_smarts") + reactantSmarts: str = Field(None, alias="reactants_smarts") + reactions: List['SimpleReaction'] = Field([], alias="related_reactions") + reviewStatus: str = Field(None, alias="review_status") + scenarios: List['SimpleScenario'] = Field([], alias="scenarios") + smirks: str = Field("", alias="smirks") + # TODO + transformations: str = Field("", alias="transformations") + + @staticmethod + def resolve_url(obj: Rule): + return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-') + + @staticmethod + def resolve_identifier(obj: Rule): + if 'simple-rule' in obj.url: + return 'simple-rule' + if 'simple-ambit-rule' in obj.url: + return 'simple-rule' + elif 'parallel-rule' in obj.url: + return 'parallel-rule' + elif 'sequential-rule' in obj.url: + return 'sequential-rule' + else: + return None + + @staticmethod + def resolve_review_status(obj: Rule): + return 'reviewed' if obj.package.reviewed else 'unreviewed' + + @staticmethod + def resolve_product_filter_smarts(obj: Rule): + return obj.product_filter_smarts if obj.product_filter_smarts else '' + + @staticmethod + def resolve_reactant_filter_smarts(obj: Rule): + return obj.reactant_filter_smarts if obj.reactant_filter_smarts else '' + + +class CompositeRuleSchema(Schema): + aliases: List[str] = Field([], alias="aliases") + description: str = Field(None, alias="description") + ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers") + id: str = Field(None, alias="url") + identifier: str = Field(None, alias="identifier") + isCompositeRule: bool = True + name: str = Field(None, alias="name") + pathways: List['SimplePathway'] = Field([], alias="related_pathways") + productFilterSmarts: str = Field("", alias="product_filter_smarts") + reactantFilterSmarts: str = Field("", alias="reactant_filter_smarts") + reactions: List['SimpleReaction'] = Field([], alias="related_reactions") + reviewStatus: str = Field(None, alias="review_status") + scenarios: List['SimpleScenario'] = Field([], alias="scenarios") + simpleRules: List['SimpleRule'] = Field([], alias="simple_rules") + + @staticmethod + def resolve_ec_numbers(obj: Rule): + return [] + + @staticmethod + def resolve_url(obj: Rule): + return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-') + + @staticmethod + def resolve_identifier(obj: Rule): + if 'simple-rule' in obj.url: + return 'simple-rule' + if 'simple-ambit-rule' in obj.url: + return 'simple-rule' + elif 'parallel-rule' in obj.url: + return 'parallel-rule' + elif 'sequential-rule' in obj.url: + return 'sequential-rule' + else: + return None + + @staticmethod + def resolve_review_status(obj: Rule): + return 'reviewed' if obj.package.reviewed else 'unreviewed' + + @staticmethod + def resolve_product_filter_smarts(obj: Rule): + return obj.product_filter_smarts if obj.product_filter_smarts else '' + + @staticmethod + def resolve_reactant_filter_smarts(obj: Rule): + return obj.reactant_filter_smarts if obj.reactant_filter_smarts else '' + + +@router.get("/rule", response={200: RuleWrapper, 403: Error}) +def get_rules(request): + qs = Rule.objects.none() + for p in PackageManager.get_reviewed_packages(): + qs |= Rule.objects.filter(package=p) + return {'rule': qs} + + +@router.get("/package/{uuid:package_uuid}/rule", response={200: RuleWrapper, 403: Error}) +def get_package_rules(request, package_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return {'rule': Rule.objects.filter(package=p).prefetch_related('package')} + except ValueError: + return 403, { + 'message': f'Getting Rules for Package with id {package_uuid} failed due to insufficient rights!'} + + +@router.get("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", + response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error}) +def get_package_rule(request, package_uuid, rule_uuid): + return _get_package_rule(request, package_uuid, rule_uuid) + + +@router.get("/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}", + response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error}) +def get_package_simple_rule(request, package_uuid, rule_uuid): + return _get_package_rule(request, package_uuid, rule_uuid) + + +@router.get("/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}", + response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error}) +def get_package_parallel_rule(request, package_uuid, rule_uuid): + return _get_package_rule(request, package_uuid, rule_uuid) + + +def _get_package_rule(request, package_uuid, rule_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return Rule.objects.get(package=p, uuid=rule_uuid) + except ValueError: + return 403, { + 'message': f'Getting Rule with id {rule_uuid} failed due to insufficient rights!'} + + +# POST +@router.post("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}) +def post_package_rule(request, package_uuid, rule_uuid, compound: Form[str] = None): + return _post_package_rule(request, package_uuid, rule_uuid, compound=compound) + + +@router.post("/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}) +def post_package_simple_rule(request, package_uuid, rule_uuid, compound: Form[str] = None): + return _post_package_rule(request, package_uuid, rule_uuid, compound=compound) + + +@router.post("/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}) +def post_package_parallel_rule(request, package_uuid, rule_uuid, compound: Form[str] = None): + return _post_package_rule(request, package_uuid, rule_uuid, compound=compound) + + +def _post_package_rule(request, package_uuid, rule_uuid, compound: Form[str]): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + r = Rule.objects.get(package=p, uuid=rule_uuid) + + if compound is not None: + if not compound.split(): + return 400, {'message': 'Compound is empty'} + + product_sets = r.apply(compound) + + res = [] + for p_set in product_sets: + for product in p_set: + res.append(product) + + return HttpResponse('\n'.join(res), content_type="text/plain") + + return r + + except ValueError: + return 403, { + 'message': f'Getting Rule with id {rule_uuid} failed due to insufficient rights!'} + + +############ +# Reaction # +############ +class SimpleReaction(SimpleObject): + identifier: str = 'reaction' + + +class ReactionWrapper(Schema): + reaction: List['SimpleReaction'] + + +class ReactionCompoundStructure(Schema): + compoundName: str = Field(None, alias="name") + id: str = Field(None, alias="url") + smiles: str = Field(None, alias="smiles") + + +class ReactionSchema(Schema): + aliases: List[str] = Field([], alias="aliases") + description: str = Field(None, alias="description") + ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers") + educts: List['ReactionCompoundStructure'] = Field([], alias="educts") + id: str = Field(None, alias="url") + identifier: str = 'reaction' + medlineRefs: List[str] = Field([], alias="medline_references") + multistep: bool = Field(None, alias="multi_step") + name: str = Field(None, alias="name") + pathways: List['SimplePathway'] = Field([], alias="related_pathways") + products: List['ReactionCompoundStructure'] = Field([], alias="products") + references: List[Dict[str, List[str]]] = Field([], alias="references") + reviewStatus: str = Field(None, alias="review_status") + scenarios: List['SimpleScenario'] = Field([], alias="scenarios") + smirks: str = Field("", alias="smirks") + + @staticmethod + def resolve_smirks(obj: Reaction): + return obj.smirks() + + @staticmethod + def resolve_ec_numbers(obj: Reaction): + # TODO fetch via scenario EnzymeAI + return [] + + @staticmethod + def resolve_references(obj: Reaction): + # TODO + return [] + + @staticmethod + def resolve_medline_references(obj: Reaction): + # TODO + return [] + + @staticmethod + def resolve_review_status(obj: Rule): + return 'reviewed' if obj.package.reviewed else 'unreviewed' + + +@router.get("/reaction", response={200: ReactionWrapper, 403: Error}) +def get_reactions(request): + qs = Reaction.objects.none() + for p in PackageManager.get_reviewed_packages(): + qs |= Reaction.objects.filter(package=p) + return {'reaction': qs} + + +@router.get("/package/{uuid:package_uuid}/reaction", response={200: ReactionWrapper, 403: Error}) +def get_package_reactions(request, package_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return {'reaction': Reaction.objects.filter(package=p).prefetch_related('package')} + except ValueError: + return 403, { + 'message': f'Getting Reactions for Package with id {package_uuid} failed due to insufficient rights!'} + + +@router.get("/package/{uuid:package_uuid}/reaction/{uuid:reaction_uuid}", response={200: ReactionSchema, 403: Error}) +def get_package_reaction(request, package_uuid, reaction_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return Reaction.objects.get(package=p, uuid=reaction_uuid) + except ValueError: + return 403, { + 'message': f'Getting Reaction with id {reaction_uuid} failed due to insufficient rights!'} + + +############ +# Scenario # +############ +class SimpleScenario(SimpleObject): + identifier: str = 'scenario' + + +class ScenarioWrapper(Schema): + scenario: List['SimpleScenario'] + + +class ScenarioSchema(Schema): + aliases: List[str] = Field([], alias="aliases") + collection: Dict['str', List[Dict[str, Any]]] = Field([], alias="collection") + collectionID: Optional[str] = None + description: str = Field(None, alias="description") + id: str = Field(None, alias="url") + identifier: str = 'scenario' + linkedTo: List[Dict[str, str]] = Field({}, alias="linked_to") + name: str = Field(None, alias="name") + pathways: List['SimplePathway'] = Field([], alias="related_pathways") + relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios") + reviewStatus: str = Field(None, alias="review_status") + scenarios: List['SimpleScenario'] = Field([], alias="scenarios") + type: str = Field(None, alias="scenario_type") + + @staticmethod + def resolve_collection(obj: Scenario): + return obj.additional_information + + @staticmethod + def resolve_review_status(obj: Rule): + return 'reviewed' if obj.package.reviewed else 'unreviewed' + + +@router.get("/scenario", response={200: ScenarioWrapper, 403: Error}) +def get_scenarios(request): + qs = Scenario.objects.none() + for p in PackageManager.get_reviewed_packages(): + qs |= Scenario.objects.filter(package=p) + return {'scenario': qs} + + +@router.get("/package/{uuid:package_uuid}/scenario", response={200: ScenarioWrapper, 403: Error}) +def get_package_scenarios(request, package_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return {'scenario': Scenario.objects.filter(package=p).prefetch_related('package')} + except ValueError: + return 403, { + 'message': f'Getting Scenarios for Package with id {package_uuid} failed due to insufficient rights!'} + + +@router.get("/package/{uuid:package_uuid}/scenario/{uuid:scenario_uuid}", response={200: ScenarioSchema, 403: Error}) +def get_package_scenario(request, package_uuid, scenario_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return Scenario.objects.get(package=p, uuid=scenario_uuid) + except ValueError: + return 403, { + 'message': f'Getting Scenario with id {scenario_uuid} failed due to insufficient rights!'} + + +########### +# Pathway # +########### +class SimplePathway(SimpleObject): + identifier: str = 'pathway' + +class PathwayWrapper(Schema): + pathway: List['SimplePathway'] + +@router.get("/pathway", response={200: PathwayWrapper, 403: Error}) +def get_pathways(request): + qs = Pathway.objects.none() + for p in PackageManager.get_reviewed_packages(): + qs |= Pathway.objects.filter(package=p) + return {'pathway': qs} + + +@router.get("/package/{uuid:package_uuid}/pathway", response={200: PathwayWrapper, 403: Error}) +def get_package_pathways(request, package_uuid): + try: + p = PackageManager.get_package_by_id(request.user, package_uuid) + return {'pathway': Pathway.objects.filter(package=p).prefetch_related('package')} + except ValueError: + return 403, { + 'message': f'Getting Pathways for Package with id {package_uuid} failed due to insufficient rights!'} + + +# @router.get("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}", response={200: Pathway, 403: Error}) +# def get_package_pathway(request, package_uuid, pathway_uuid): +# try: +# p = PackageManager.get_package_by_id(request.user, package_uuid) +# return Pathway.objects.get(package=p, uuid=pathway_uuid) +# except ValueError: +# return 403, { +# 'message': f'Getting Pathway with id {pathway_uuid} failed due to insufficient rights!'} \ No newline at end of file diff --git a/epdb/middleware/login_required_middleware.py b/epdb/middleware/login_required_middleware.py index 0c33001c..6aae3e8c 100644 --- a/epdb/middleware/login_required_middleware.py +++ b/epdb/middleware/login_required_middleware.py @@ -2,7 +2,6 @@ from django.conf import settings from django.shortcuts import redirect from django.urls import reverse - class LoginRequiredMiddleware: def __init__(self, get_response): self.get_response = get_response @@ -11,6 +10,7 @@ class LoginRequiredMiddleware: reverse('logout'), reverse('admin:login'), reverse('admin:index'), + '/api/legacy/' ] + getattr(settings, 'LOGIN_EXEMPT_URLS', []) def __call__(self, request): diff --git a/epdb/models.py b/epdb/models.py index 37d73c61..fb4baf97 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -889,6 +889,23 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti def as_svg(self, width: int = 800, height: int = 400): return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height) + @property + def related_pathways(self): + pathways = Node.objects.filter(node_labels__in=[self]).values_list('pathway', flat=True) + return Pathway.objects.filter(package=self.compound.package, id__in=set(pathways)).order_by('name') + + @property + def related_reactions(self): + return ( + Reaction.objects.filter(package=self.compound.package, educts__in=[self]) + | + Reaction.objects.filter(package=self.compound.package, products__in=[self]) + ).order_by('name') + + @property + def is_default_structure(self): + return self.compound.default_structure == self + class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin): package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True) diff --git a/utilities/chem.py b/utilities/chem.py index fae01903..0f84c99b 100644 --- a/utilities/chem.py +++ b/utilities/chem.py @@ -8,7 +8,7 @@ from indigo import Indigo, IndigoException, IndigoObject from indigo.renderer import IndigoRenderer from rdkit import Chem from rdkit import RDLogger -from rdkit.Chem import MACCSkeys +from rdkit.Chem import MACCSkeys, Descriptors from rdkit.Chem import rdChemReactions from rdkit.Chem.Draw import rdMolDraw2D from rdkit.Chem.MolStandardize import rdMolStandardize @@ -67,6 +67,18 @@ class PredictionResult(object): class FormatConverter(object): + @staticmethod + def mass(smiles): + return Descriptors.MolWt(FormatConverter.from_smiles(smiles)) + + @staticmethod + def charge(smiles): + return Chem.GetFormalCharge(FormatConverter.from_smiles(smiles)) + + @staticmethod + def formula(smiles): + return Chem.rdMolDescriptors.CalcMolFormula(FormatConverter.from_smiles(smiles)) + @staticmethod def from_smiles(smiles): return Chem.MolFromSmiles(smiles) @@ -79,6 +91,10 @@ class FormatConverter(object): def InChIKey(smiles): return Chem.MolToInchiKey(FormatConverter.from_smiles(smiles)) + @staticmethod + def InChI(smiles): + return Chem.MolToInchi(FormatConverter.from_smiles(smiles)) + @staticmethod def canonicalize(smiles: str): return FormatConverter.to_smiles(FormatConverter.from_smiles(smiles), canonical=True)