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

Feature Request: Table of unit_choices conversions within Form Widget #38

Open
jacklinke opened this issue Nov 30, 2021 · 15 comments
Open
Labels
blocked Blocked by an external issue enhancement

Comments

@jacklinke
Copy link
Contributor

jacklinke commented Nov 30, 2021

Because the value of an item using QuantityFormField defaults to the base unit when updating, even if the user originally entered a value using some other unit from unit_choices, they may later have some confusion about what value they originally entered.

To alleviate this, providing an optional table with converted values from the base unit to each of the unit choices within the form widget would be helpful. For configuration, it should only require something like setting show_table=True in the form field definition of a Django Form, defaulting to False if the attribute is not provided.

In cases where end users may set a value in a form and then need to edit that value in the future, this would help them to identify the current value since they can see both the base unit (in the input field itself) and their preferred unit (as well as any other unit choices) in the table below the input.

Additionally, users of django-pint should be able to override the template used for this table within the widget to meet their needs.


If all of this sounds like something that would be beneficial, please let me know and I'll submit a PR to provide the functionality and update the readme.

@CarliJoy
Copy link
Owner

Dear jacklinke,

I agree with your analysis and the idea sounds awesome but also a bit tricky because you would need to link the chosen units with new database fields.
Unfortunaetly it isn't possible to generate more than one database field from one django field.
So the settings would to have be done only in the django form.

I also don't yet fully understand what your definition of "table" is?
Do you want to use a database table and link each values to it? In this case we again would need to track which value of the field used belongs to which which value in my target table.
Instead I would prefer handling it with a field naming scheme i.e:

class Beer:
   volume = QuantityField(...)
   volume_unit = models.CharField()+
   voulme_original = models.Float()

Or do you mean to use an Relation like suggested here:
https://stackoverflow.com/questions/57590136/multiple-django-fields-reflects-to-one-db-field

@jacklinke
Copy link
Contributor Author

jacklinke commented Nov 30, 2021

I was thinking something even simpler than that - an actual html table of the conversions to each unit in unit_choices, displayed in the widget (optionally). The conversions would be done on-the-fly when displaying a form field using pint, of course. Something like this:

Screenshot from 2021-11-30 07-02-40

Users of django-pint could then style the table with css, or override the widget template to do things like move the table to be above the input box, for instance.

(I'm also intrigued by the concept you mention above and will think through how that might also be approached for as a future initiative)

@CarliJoy
Copy link
Owner

Ah okay. I understand now. Looks good and simple.

But then the naming is a bit confusing. Maybe call the argument show_conversion?
And the make it an enum which currently is only has "Do not show" and "Table".
So in the future we could add other tempalte if required.

I happy to look into your merge request.

@jacklinke
Copy link
Contributor Author

Great suggestions! I'll finish this up and get a PR in this week :)

@CarliJoy
Copy link
Owner

If you need any help, let me know.

@mikeford3
Copy link
Contributor

@jacklinke, are you still expecting to submit a PR for this? It would be a very nice addition to the library :)

@jacklinke
Copy link
Contributor Author

@mikeford3 It had slipped off my radar a bit, but yes will work o finishing it up this week.

@CarliJoy
Copy link
Owner

Dear jacklinke,

I agree with your analysis and the idea sounds awesome but also a bit tricky because you would need to link the chosen units with new database fields. Unfortunaetly it isn't possible to generate more than one database field from one django field. So the settings would to have be done only in the django form.

I also don't yet fully understand what your definition of "table" is? Do you want to use a database table and link each values to it? In this case we again would need to track which value of the field used belongs to which which value in my target table. Instead I would prefer handling it with a field naming scheme i.e:

class Beer:
   volume = QuantityField(...)
   volume_unit = models.CharField()+
   voulme_original = models.Float()

Or do you mean to use an Relation like suggested here: https://stackoverflow.com/questions/57590136/multiple-django-fields-reflects-to-one-db-field

Some more links to this approach:
Hack: https://blog.elsdoerfer.name/2008/01/08/fuzzydates-or-one-django-model-field-multiple-database-columns/
Django Issue: https://code.djangoproject.com/ticket/5929 (Currently down, so you can use https://web.archive.org/web/20200809115733/https://code.djangoproject.com/ticket/5929)

@mikeford3
Copy link
Contributor

Thanks @jacklinke!

I interpreted table as just an HTML table, rather than a table in a database.

I was hoping to use this display a small table, like the one Jack showed above, when the user hovers over a field.
I thought I'd try to use django-pint in the view to handle the conversions from base units (or the units the field currently uses) to the other unit_choices, and how to represent the units. The view would then be able to give the template a set of magnitudes and units for each field in the context variable.

@jacklinke
Copy link
Contributor Author

jacklinke commented May 26, 2022

@mikeford3 & @CarliJoy I think this issue should focus on the displayed table, with the changes to the db as a separate issue.

With that in mind, I'm nearly done with the work to display a table. Code still needs some tweaks and cleanup, but here's a screenshot I took just now using the dummyapp, using:

weight = QuantityFormField(
    base_units="gram",
    unit_choices=["ounce", "gram", "pound", "ton", "kilogram", "milligram"],
    show_conversion=DisplayOption.TABLE,
)

Screenshot from 2022-05-26 07-21-41


The html template for the widget looks like this

{% spaceless %}{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}{% endspaceless %}

{% if values_list %}
    <table style="margin:20px;">
        <tr>
            <th>Unit</th>
            <th>Value</th>
        </tr>
        {% for value_item in values_list %}
            <tr>
                <td>{{ value_item.units }}</td>
                <td style="text-align:right">
                    {{ value_item.magnitude|floatformat:6 }}
                </td>
            </tr>
        {% endfor %}
    </table>
{% else %}
    <br>
{% endif %}

@CarliJoy
Copy link
Owner

Looks nice. I agree that the DB issue is just something very different.

@mikeford3
Copy link
Contributor

Hi @jacklinke, is still this something that you're working on?

@apaxson
Copy link

apaxson commented Aug 22, 2023

I, too, am looking at enhancing the QuantityField, where the selected magnitude is actually stored with the data. Except, the ideas I've had would render the DB value near useless for any external access/reporting tools. The easiest is to pickle the object. Or, more flexible, converting the Long field to a CharField and appending the magnitude to the value. You would obviously lose the precision characteristics of a Long, but gain descriptive value.

i.e. base_unit = minutes, selected = 8 days = "11520:days"

@CarliJoy
Copy link
Owner

"Pickling" numbers as strings in a database is not a good idea.
You will increase the needed storage by multitude and loose all sql math capabilities like sum, average etc...

As long https://code.djangoproject.com/ticket/5929 is not resolved I don't see a proper way to solve this issue properly.

@CarliJoy CarliJoy added enhancement blocked Blocked by an external issue labels Aug 22, 2023
@apaxson
Copy link

apaxson commented Aug 24, 2023

I agree about Pickling. It's an idea, but a bad one. Until we can do this "proper", I've written a custom field that just uses markup in the DB. Horrible for external tools to the DB, but it works for me. Looking forward to solving this without markup.

i.e. with base_units = 'kilograms', saving an object using 'pounds'

# ureg= UnitRegistry()
# weight = 1000 * ureg.lb
# weight
<Quantity(1000, 'pound')>

# car = Car()
# car.name = "testcar"
# car.weight = weight
# car.save()
get_prep_value(): return value=453.5923700000001:pound

# new_car = Car.objects.get(pk=1)
from_db_value(): return value=1000.0000000000001 pound

# new_car.weight
<Quantity(1000.0, 'pound')>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked Blocked by an external issue enhancement
Projects
None yet
Development

No branches or pull requests

4 participants