Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add fork of django unicorn 🦄 #702

Merged
merged 4 commits into from
Nov 14, 2024
Merged
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
10 changes: 9 additions & 1 deletion envs/conda/dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ dependencies:
- django-crispy-forms >=1.13.0, <=1.14.0
- django-pandas >=0.6.6, <=0.6.6
- django-filter >=21.1, <=22.1
- django_unicorn >=0.59.0, <=0.59.0
#
# A fork of django-unicorn was made, which we now use + its deps
# - django_unicorn >=0.59.0, <=0.59.0
- beautifulsoup4 >=4.8.0
- orjson >=3.6.0
- shortuuid >=1.0.1
- cachetools >=4.1.1
- decorator >=4.4.2
#
- django-simple-history >=3.3.0, <=3.3.0
- djangorestframework >=3.13.1, <3.15.2
- ipython >=8.22.2, <=8.29.0
Expand Down
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ dependencies=[
"django-contrib-comments >=2.2.0, <=2.2.0", # for tracking user comments in web
"django-crispy-forms >=1.13.0, <=1.14.0", # for formatting of online forms
"django-pandas >=0.6.6, <=0.6.6", # for converting QuerySets to PandasDataFrames
"django-unicorn >=0.59.0, <=0.59.0", # for responsive web UI (AJAX calls)
"dj-database-url >=0.5.0, <1.4.0", # for DigitalOcean URL conversion
"django-simple-history >=3.3.0, <=3.3.0", # for tracking changes to data
"djangorestframework >=3.13.1, <3.15.2", # for our REST API
Expand All @@ -99,6 +98,14 @@ dependencies=[
# "rdkit", # cheminformatics library # BUG: install is broken on pypi
# "ipython", # for rendering molecules in output
# "umap-learn", # for chemspace mapping
#
# A fork of django-unicorn was made, which we now use + its deps
# "django-unicorn >=0.59.0, <=0.59.0", # for responsive web UI (AJAX calls)
"beautifulsoup4 >=4.8.0",
"orjson >=3.6.0",
"shortuuid >=1.0.1",
"cachetools >=4.1.1",
"decorator >=4.4.2",
]

# optional dependencies that are not installed by default
Expand Down
3 changes: 2 additions & 1 deletion src/simmate/configuration/django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
#
# Django unicorn acts as a frontend framework for making dyanmic webpages
# (i.e. AJAX calls can be made to update the views)
"django_unicorn",
# "django_unicorn",
"simmate.website.configs.UnicornConfig", # fork of django_unicorn
#
# Django simple history lets you track history of changes (and who made
# those changes) for a given model. This is important for models that users
Expand Down
4 changes: 4 additions & 0 deletions src/simmate/website/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class DataExplorerConfig(AppConfig):
name = "simmate.website.data_explorer"


class UnicornConfig(AppConfig):
name = "simmate.website.unicorn"


class UserTrackingConfig(AppConfig):
name = "simmate.website.user_tracking"

Expand Down
12 changes: 11 additions & 1 deletion src/simmate/website/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,17 @@ def get_disabled_urls():
path(route="about/", view=views.about, name="about"),
#
# Django-unicorn urls
path("unicorn/", include("django_unicorn.urls")),
path(
route="unicorn/",
view=include(
(
"simmate.website.unicorn.urls",
"simmate.website.unicorn",
),
namespace="unicorn",
),
name="unicorn",
),
#
# Django-contrib-comments urls
path("comments/", include("django_comments.urls")),
Expand Down
4 changes: 2 additions & 2 deletions src/simmate/website/core_components/components/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import urllib

from django.shortcuts import redirect
from django_unicorn.components import UnicornView

from simmate.database.base_data_types import DatabaseTable
from simmate.toolkit import Molecule, Structure
from simmate.website.unicorn.components import UnicornView
from simmate.website.utilities import parse_request_get


Expand Down Expand Up @@ -442,7 +442,7 @@ def set_property(
# attempt casting to correct type
new_value = parse_value(new_value)
# buggy
# from django_unicorn.typer import cast_attribute_value
# from simmate.website.unicorn.typer import cast_attribute_value
# new_value = cast_attribute_value(self, property_name, new_value)

# check if there is a special defined method for this property
Expand Down
2 changes: 1 addition & 1 deletion src/simmate/website/data_explorer/components/api_filter.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from django.shortcuts import redirect
from django_unicorn.components import UnicornView

# from simmate.configuration import settings
from simmate.database.base_data_types import DatabaseTable, FilteredScope, table_column
from simmate.website.data_explorer.views import EXPLORABLE_TABLES
from simmate.website.unicorn.components import UnicornView
from simmate.website.utilities import parse_request_get

# TODO: move to util and combine with var used in views.py
Expand Down
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions src/simmate/website/unicorn/actions/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# order of imports is important to prevent circular deps
from .base import BackendAction
from .call_method import CallMethod
from .refresh import Refresh
from .reset import Reset
from .set_attribute import SetAttribute
from .sync_input import SyncInput
from .toggle import Toggle
from .validate import Validate
120 changes: 120 additions & 0 deletions src/simmate/website/unicorn/actions/backend/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from abc import ABC, abstractmethod

from simmate.website.unicorn.actions.frontend import FrontendAction
from simmate.website.unicorn.components import Component


class BackendAction(ABC):
"""
Abstract base class for Unicorn Actions that get queued & applied to components
in the backend (via python). This base class also has helper methods for
dynamically loading various Action types.

This class and its methods are typically handled by the FrontendAction class
"""

# --- Abstract methods / attrs that must be set in the subclass ---

action_type: str = None
"""
A name for this action that is used in JSON from the frontend
"""

@abstractmethod
def apply(
self,
component: Component,
request, # : ComponentRequest,
) -> tuple[Component, FrontendAction]:
"""
Applies the update to the component and returns the ActionResult if
there is one. Must be defined in all subclasses.
"""
raise NotImplementedError()

# --- Built-in methods ---

def __init__(self, payload: dict, partials: list):
self.payload = payload if payload is not None else {}
self.partials = partials if partials is not None else []

def __repr__(self):
return (
f"BackEnd Action: {self.__class__.__name__}"
f"(action_type='{self.action_type}' payload={self.payload} "
f"partials={self.partials})"
)

def to_dict(self) -> dict:
"""
Converts the Action back to a dictionary match what the frontend gives
"""
return {
"partials": self.partials,
"payload": self.payload,
"type": self.action_type,
}

@classmethod
def from_dict(cls, data: dict):
expected_type = data.get("type")
if expected_type != cls.action_type:
raise ValueError(
f"Action type mismatch. Type '{expected_type}' "
f"was provided, but class only accepts '{cls.action_type}'"
)
return cls(
payload=data.get("payload", {}),
partials=data.get("partials", []),
)

# --- Utility methods that help interact with *all* Action subclasses ---

@classmethod
def from_many_dicts(cls, data: list[dict]):
"""
Given a list of config dictionaries, this will create and return
a list Action objects in the proper Action subclass.

This input is typically grabbed directly from `request.body.actionQueue`
"""

mappings = cls.get_action_type_mappings()

actions = []
for config in data:
action_type = config["type"]

if action_type not in mappings.keys():
raise ValueError(f"Unknown Action type: '{action_type}'")

action_class = mappings[action_type]
action = action_class.from_dict(config)
actions.append(action)

return actions

def get_action_type_mappings() -> dict:
"""
Gives a mapping of action_type to the Action subclass that should be
used. For example: {"callMethod": simmate.website.unicorn.actions.backend.CallMethod}
"""
# TODO: We assume only internal Actions for now, but we may want to
# support customer user Actions.

# local import to prevent circular deps
from simmate.website.unicorn.actions.backend import CallMethod, SyncInput

return {
action.action_type: action
for action in [
CallMethod,
SyncInput,
# !!! These are special cases.
# See CallMethod.from_dict for details.
# Refresh,
# Reset,
# Toggle,
# Validate,
]
}
Loading
Loading