-
Notifications
You must be signed in to change notification settings - Fork 1
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
How to handle Django models.TextChoice with Djelm ? #99
Comments
Hi @MikHulk! Thanks for this issue! It's a juicy one! Love it!
Yes!! You are completely correct! If you give a custom type variants that all resolve to There are a bunch of ways we could solve for this I'll just put my thoughts here and would love to hear from you what you think feels the right way, or your ideas on how you would solve it. LiteralFlagit might be handy if we had a CustomTypeFlag(
[
("Created", LiteralFlag("created")),
("Ongoing", LiteralFlag("in progress")),
("Cancelled", LiteralFlag("cancelled")),
("Suspended", LiteralFlag("suspended")),
("Finished", LiteralFlag("finished")),
("Unknown", String()), # <-- Catch all
] Extend StringFlagWe could add some config to CustomTypeFlag(
[
("Created", StringFlag(literal="created"))
...
]
) TextChoices flagWe could create a utility for the django TextChoicesFlag(StoryStatus) |
To solve your issue currently you could disambiguate the value that comes through. It's a tiny bit hacky but it would make djelm resolve the values correctly. Consider the following! For demonstration purposes it assumes a very simple context, but you could translate this to the actual context you have. In your view: def some_view():
user_story = UserStory.objects.get(pk=1)
context = dict([(user_story.status, user_story.status)]) # <-- { "created": "created"}
# flags
CustomTypeFlag(
[
("Created", ObjectFlag({"created": StringFlag()})),
...
] This doesn't solve for a catch all unfortunately, it would cause an error. Maybe that's ok though? Because you are using In fact in a form, Django I believe would reject anything that is not in that enumeration during form validation. Let me know if this hacky suggestion gets you some results! |
Hi !
Yes I just add this on Elm side because without Elm complains on the case of in For your suggestion for a new flag my first idea was TextChoicesFlag indeed (I called it EnumFlag but idea it's the same). Just keep in mind that it exists an IntegerChoice too and that is possible to subclasse choice. We could have something like: ChoiceFlag(StoryStatus, StringFlag()) Or maybe it's possible to deduce that directly from the class passed to ChoiceFlag. That said I like the idea of LiteralFlag or even the possibility of configuring StringFlag. |
Yea that's a good point! I imagine it would be possible to deduce, python is great like that. I also like the idea of The only thing that bothers me is that |
I have just tried your workaround and I obtain this model: type Epic_Stories__Status__
= Created Epic_Stories__Status__Created___
| OnGoing Epic_Stories__Status__OnGoing___
| Suspended Epic_Stories__Status__Suspended___
| Cancelled Epic_Stories__Status__Cancelled___
| Finished Epic_Stories__Status__Finished___
type alias Epic_Stories__Status__Created___ =
{ created : String }
type alias Epic_Stories__Status__OnGoing___ =
{ in_progress : String }
type alias Epic_Stories__Status__Suspended___ =
{ suspended : String }
type alias Epic_Stories__Status__Cancelled___ =
{ canceled : String }
type alias Epic_Stories__Status__Finished___ =
{ finished : String } In some way it works but it's a bit hard to pattern match it . Idealy it would be very cool to get this: type Status
= Created
| OnGoing
| Cancelled
| Suspended
| Finished I'll try to see how the generation works tomorrow. To get an idea of the feasibility of one of the 3 solutions you have proposed. I think it would be really nice to have this possibility. |
Yep that looks right! The pattern matching as I see it would be as follows: doSomethingWithStatus : Epic_Stories__Status__ -> something
doSomethingWithStatus status =
case status of
Created _ ->
-- Do something with created
... This doesn't seem too bad to me, but I would love to understand what about it feels tricky for you. Perhaps we might be able to improve it! 😄 The parameter is probably not important which is why I In regards to the naming, Djelm protects all top level type declarations, otherwise it would be incredibly easy to get name clashes like the following: type alias Status =
{ something : String
, status : Status
}
type Status
= Something The result of this protection means the If this becomes a problem you could always just map the type to something more concise. type Status
= Created
...
mapToStatus : Epic_Stories__Status__ -> Status
mapToStatus storiesStatus =
case storiesStatus of
Model.Created _ ->
Created
.... To me though, it doesn't seem like this adds much other than a prettier type name. Let me know what you think! |
having type Epic_Stories__Status_
= Created
| OnGoing
| Cancelled
| Suspended
| Finished It's mainly the fact of having compound variants that's a bit cumbersome. In my case I need to use these types and sometimes instantiate them from the frontend. For example, here. One might also need to use this type in another composite type. In my case, I need them in Msg. That said, to be perfectly clear, this would be a nice improvement. And of course it's perfectly possible to work with Djelm as it is now. |
Oh! Yes I can totally understand from an instantiation point of view, that is cumbersome indeed! I 100% agree that it would be a nice improvement, and I'm totally onboard with making it happen. I'm going to think about a nice way to achieve this! It's a good enhancement! |
That's great ! Please, let me know about your investigations. 😄 |
Ok I have thought about this and I think I have a good idea. Step 1 String literalsOne thing that I think is most important is that the Elm decoding should exactly match the pydantic validation. This means that if we are declaring a string literal validation in python via pydantic, then the Elm decoders should also express that same constraint. The reason this is important is that the Elm programs should be able to receive JSON from anywhere and still behave exactly the same. Put simply, Elm decoding should be mirror python validation, always! (Currently it does). The Elm analogue for string literal validation roughly translates as follows: Decode.string |> Decode.andThen matchLitereal The Djelm compiler makes it possible to easily create these functions inline. We would use the op module for this. Step 2 CustomType constructors with no valuesI'm still thinking about this and I will have a clearer idea after we get string literals on how we could express it. Im flying home from Spain on the weekend so im going to see if I can smash it out in the air! Wish me luck! |
Have a nice and fruitful trip from Spain ! I believe you're on the right path with LiteralFlag.
100% agree with that I already have needed to use the model to validate JSON received from the DRF API . So I think this point is important. The main issue is Django represents choice with two "kind" of value the python value used in python code and its representation (an example). DRF returns the representation by default but from python code the enum variant are used unde their "real" name. So I am not sure about how to handle this on Elm side. |
Awesome!
Could you add a clear example of this? It's difficult for me to visualize exactly what you mean, but perhaps if there were examples you could show I might be able to help out more. |
of course exactly here . In my project I have one or several User Stories given to the Elm page by Django but sometime I need to refresh one of them either because the user has just modified it with an action, or because it has been changed by someone else. DomainError is a variant for a specific error my DRF api returns when an operation doesn't match the domain rules (typically an invalid change from an old to a new status). The status in the UserStory is a Django TextChoice and it is handled directly by DRF . This is an example of the content DRF provides: {
"id": 11,
"url": "http://127.0.0.1:8000/epics-api/stories/11/",
"pub_date": "2024-05-10T20:12:16Z",
"title": "title",
"description": "bla bla bla.",
"epic": "http://127.0.0.1:8000/epics-api/epics/1/",
"assigned_to": null,
"status": "in progress"
} As you see status is given with its string representation and not with its python value. This is expected from an api rest point of view, but may not be easy for Djelm to handle if it expects the actual value of the python enum (i.e. StoryStatus.IN_PROGRESS in this case). |
Thanks for the link! It's cool you are building this with DRF, I actually didn't think of this use case! I have always worked with Django as an MPA and used HTMX to make it feel like an SPA. There are a lot of things that Djelm could do more of to help with this type of application. Nice!
The DRF returning a string for the TextChoice actually doesn't matter for Djelm or the If we build a |
Hi !
I'm having a bit of a struggle using django's choices field from Elm.
From django I have this model:
If I try to use a CustomType there is no mean for djelm to distinguish them since there are all strings and it seems it chooses the first when converting.
So I had to use simple StringFlag and convert string from Elm type directly from elm code.
For that, I created an explicit Elm type:
and an utility function to convert string from django to the elm proper variant:
Note I need an additional variant in case of an unknown choice potentially provided by django.
(This could be useful for detecting a change in the possible choices on the django side that would not have been reflected in the front-end code.)
I do understand that this is a tricky point to tackle, that said.
Maybe we could have an EnumFlag something like that.
Any idea ?
For full example see these commits:
https://github.com/MikHulk/epics/commit/307b9b821aa5bb116b1415994910b126ef60a778
https://github.com/MikHulk/epics/commit/8aee32611c8a35e8190642935cb3e296ee9de47e
The text was updated successfully, but these errors were encountered: