Coder Social home page Coder Social logo

django-prices's Introduction

django-prices: Django fields for the prices module

Build Status codecov.io

Installation

  • pip install django-prices
  • Add django_prices to your INSTALLED_APPS at settings.py
  • Follow enmerkar instructions and update both your INSTALLED_APPS and MIDDLEWARE_CLASSES.

Features

Provides support for models:

from django.db import models
from django_prices.models import MoneyField, TaxedMoneyField


class Model(models.Model):
    currency = models.CharField(max_length=3, default="BTC")
    price_net_amount = models.DecimalField(max_digits=9, decimal_places=2, default="5")
    price_net = MoneyField(amount_field="price_net_amount", currency_field="currency")
    price_gross_amount = models.DecimalField(
        max_digits=9, decimal_places=2, default="5"
    )
    price_gross = MoneyField(
        amount_field="price_gross_amount", currency_field="currency"
    )
    price = TaxedMoneyField(
        net_amount_field="price_net_amount",
        gross_amount_field="price_gross_amount",
        currency="currency",
    )

And forms:

from django import forms

from django_prices.forms import MoneyField

AVAILABLE_CURRENCIES = ["BTC", "USD"]


class ProductForm(forms.Form):
    name = forms.CharField(label="Name")
    price_net = MoneyField(label="net", available_currencies=AVAILABLE_CURRENCIES)

And validators:

from django import forms
from prices.forms import Money

from django_prices.forms import MoneyField
from django_prices.validators import (
    MaxMoneyValidator, MinMoneyValidator, MoneyPrecisionValidator)


class DonateForm(forms.Form):
    donator_name = forms.CharField(label="Your name")
    donation = MoneyField(
        label="net",
        available_currencies=["BTC", "USD"],
        max_digits=9,
        decimal_places=2,
        validators=[
            MoneyPrecisionValidator(),
            MinMoneyValidator(Money(5, "USD")),
            MaxMoneyValidator(Money(500, "USD")),
        ],
    )

It also provides support for templates:

{% load prices %}

<p>Price: {{ foo.price.gross|amount }} ({{ foo.price.net|amount }} + {{ foo.price.tax|amount }} tax)</p>

You can also use HTML output from prices template tags, they will wrap currency symbol in a <span> element:

{% load prices %}

<p>Price: {{ foo.price.gross|amount:'html' }} ({{ foo.price.net|amount:'html' }} + {{ foo.price.tax|amount:'html' }} tax)</p>

It will be rendered as a following structure (for example with English locale):

<span class="currency">$</span>15.00

How to migrate to django-prices 2.0

Version 2.0 introduces major changes to how prices data is stored in models, enabling setting price's currency per model instance.

Steps to migrate:

  1. In your forms:

    • remove the currency argument
    • add available_currencies with available choices.

    If the form specified MoneyFields in fields option, replace those with explicit declarations instead:

    AVAILABLE_CURRENCIES = [("BTC", "bitcoins"), ("USD", "US dollar")]
    
    class ModelForm(forms.ModelForm):
        class Meta:
            model = models.Model
            fields = []
    
        price_net = MoneyField(available_currencies=AVAILABLE_CURRENCIES)
  2. In your models using MoneyField:

    • Replace all occurrences of the MoneyField class with DecimalField

    • Remove the currency argument from them

    • Change default from Money instance to value acceptable by Decimal field

      Example of code:

      price_net = MoneyField(
          "net", currency="BTC", default=Money("5", "BTC"), max_digits=9, decimal_places=2
      )

      Updated code:

      price_net = models.DecimalField("net", default="5", max_digits=9, decimal_places=2)
  3. In your migration files:

    • Replace all occurrences of the MoneyField class with DecimalField

    • Remove the currency argument from them

    • Change default from Money instance to value acceptable by Decimal field

      field = django_prices.models.MoneyField(currency='BTC', decimal_places=2, default='5', max_digits=9, verbose_name='net')

      Updated code:

      field = models.DecimalField(decimal_places=2, default='5', max_digits=9, verbose_name='net')
  4. Rename fields in models. Your old field will still store amount of money, so probably the best choice would be price_net_amount instead price_net.

  5. All places which use Models and it's fields can prevent django app from even starting the code. Possible issues: code tries to access non existing fields. Exclude those fields for now from your ModelForms, Graphene types etc.

  6. Run python manage.py makemigrations. Make sure to do this step before adding new MoneyFields to model! If not, django will generate delete/create migrations instead of rename.

  7. Run python manage.py migrate.

  8. Update django-prices.

  9. Add models.CharField for currency and MoneyField to your models:

    currency = models.CharField(max_length=3, default="BTC")
    price_net_amount = models.DecimalField("net", default="5", max_digits=9, decimal_places=2)
    price_net = MoneyField(amount_field="price_net_amount", currency_field="currency")
  10. Run python manage.py makemigrations and python manage.py migrate.

  11. Change TaxedMoneyField declaration:

    price = TaxedMoneyField(
        net_amount_field="price_net_amount",
        gross_amount_field="price_gross_amount",
        currency="currency",
    )
  12. Remember to address changes in previously edited ModelForms

django-prices's People

Contributors

akjanik avatar artursmet avatar bharathwaaj avatar bogdal avatar derenio avatar gregmuellegger avatar itbabu avatar koradon avatar krzysztofwolski avatar kwaidan00 avatar maarcingebala avatar marekbrzoska avatar mitcom avatar mkalinowski avatar mociepka avatar patrys avatar rafalp avatar szewczykmira avatar tungd avatar zagrebelin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-prices's Issues

DecimalFields must define a 'max_digits'/'max_places' attribute.

I am getting these errors when trying to migrate:

DecimalFields must define a 'decimal_places' attribute.
DecimalFields must define a 'max_digits' attribute

My code (just like front page):

class Product(models.Model):
    name = models.CharField('Name')
    price = PriceField('Price', currency='BTC')

I am using django1.8.3.
Am i missing something? Is there any restriction for the values of those attributes?
If those attributes are required, it should be reflected on the readme.

Problem rendering number value on a form

Hi,

i try to used django_prices... work well, except when rendering form number ...

<input type="number" name="price_override" value="39,00" step="any" id="id_price_override" /> EUR

In France, value 39,00 is not a number .. how to pass "39.00" value to my form ?

Thx for help
lo

Cannot deserialize MoneyField

When trying to deserialize a model containing a MoneyField, we get the following error:

django.core.serializers.base.DeserializationError: 
'MoneyField' object has no attribute 'to_python': (product.product:pk=60) field_value was 'Money('35.98', 'USD')'

AmountField value not displaying in admin form Field

I'm running django_prices (and prices) ver 1.0.0b2 on Django1.11.9.
Upon saving an object and trying to edit in admin, all AmountField are empty. Inspecting the html code, I see that it looks like
<input type="number" name="s_price_net" value="Amount('5.00', 'NGN')" step="any" required="" id="id_s_price_net">
Is this a bug?
Any help will be appreciated.

"amount" template filter ignores LC_NUMERIC environment variable

Hi.

There is a problem using Saleor/django-prices in case with some default locales:
e.g:

LANGUAGE_CODE=ru
DEFAULT_CURRENCY=USD

Usage:
{{ price|amount:'html' }}
Output: 11,49 $

It is not valid output for USD currency (even for RU locale). The valid one is classic $11.49

Reason:
"|amount" template filter forces currency format by passing the current language to Babel as locale (see templatetags/prices_i18n.py:42).

Babel supports overriding numeric locale via LC_NUMERIC env.

It will be very useful if get_locale_data() in prices_i18n.py will be checking this env before passing currency locale to Babel.

PriceField with default, and inline models in admin

I have Parent and Child models in myapp/models.py. The MyChild model has a PriceField field:

from django.db import models
from django_prices.models import PriceField

class MyParent(models.Model):
    name = models.CharField(max_length=30)

class MyChild(models.Model):
    name = models.CharField(max_length=30)

    # note default attribute is present
    price_offset = PriceField("unit price offset", currency='USD', max_digits=12, decimal_places=4, default=0)

    parent = models.ForeignKey(MyParent, related_name='children')

And in myapp/admin.py, the child models are inline:

from django.contrib import admin
import models

class ChildInline(admin.TabularInline):
    model = models.MyChild
    extra = 3

class ParentAdmin(admin.ModelAdmin):
    inlines = (ChildInline,)
admin.site.register(models.MyParent, ParentAdmin)

If I attempt to add a Parent using the admin, by filling the Parent's name field, but leaving the three child forms untouched, I get errors because the three empty children are also being submitted.

inline_pricefield_problem

(In fact, if MyChild.name was able to validate, either having its own default value, or blank=True parameter, the form would submit successfully and three MyChild instances would be saved to the database. Three new MyChild instances would be created each time the Parent instance is saved using the admin.)

This is triggered by the presence of the default parameter of the MyChild.price_offset field. If I remove the default, I can submit without an attempt to create child instances.

Nicer rendering

I can't see an obvious way to get $5 rendered instead of $5.00 for round numbers. The equivalent of what {{ price|floatformat:"-2" }} would do.

Dynamically set currency

One of our projects requires the currency of the PriceField to be dynamically set.
Our solution is to define get_currency method on the PriceField which can be overridden by class inheritance.
master...derenio:feature/dynamic_currency

Exemplary use case:

from django.db import models
from django_prices.models import PriceField


class DynamicPriceField(PriceField):

    def get_currency(self):
        if not self.currency:
            self.currency = self.model.currency
        return self.currency

class Purchase(models.Model):
    CURRENCY_CHOICES = [
        ('USD', 'USD'),
        ('GBP', 'GBP'),
        ('PLN', 'PLN')]

    currency = models.ChoiceField(choices=CURRENCY_CHOICES)
    price =     cost = DynamicPriceField(decimal_places=2, max_digits=10, default=Decimal())

Support for selecting models

from prices import Price
p = Price("1.23")
from core.models import Item
Item.objects.create(name="foo", category_id=1, price=p)
<Item: name: foo, price: Price('1.23', currency=None)>
>>> Item.objects.get(price=p)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/models/manager.py", line 143, in get
    return self.get_query_set().get(*args, **kwargs)
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/models/query.py", line 382, in get
    num = len(clone)
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/models/query.py", line 90, in __len__
    self._result_cache = list(self.iterator())
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/models/query.py", line 301, in iterator
    for row in compiler.results_iter():
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 775, in results_iter
    for rows in self.execute_sql(MULTI):
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 840, in execute_sql
    cursor.execute(sql, params)
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/backends/util.py", line 41, in execute
    return self.cursor.execute(sql, params)
  File "/home/dagrevis/Python/managr/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py", line 362, in execute
    return Database.Cursor.execute(self, query, params)
InterfaceError: Error binding parameter 0 - probably unsupported type.

The fix would be to get Decimal in get_prep_value and use it.

What do you think? Thanks!

Integration tests with views

Codebase needs to have additional tests for rendering our custom fields in django views. Probably tests can be performed using snapshottest.

Explain that you need to enable the app

It would be great if you could add instructions that the app must be enabled to enable all the parts of it like template tags. I was looking why they didn't work for like ten minutes just now.

Thanks!

Currency column

After looking at generated SQL, I'm surprised! Where is the column for currency?

>>> Item.objects.all()[0]
<Item: name: Kkāds štrunts, price: Price('0.01', currency='LVL')>
>>> i = Item.objects.all()[0]
>>> from prices import Price
>>> p = Price("1.0", currency="USD")
>>> i.price = p
>>> i.save()
>>> i = Item.objects.all()[0]
>>> i.price
Price('1', currency='LVL')

Another place where it's possible to shoot yourself in the foot. A fix would be to add an exception when column is set to Currencyobject that has different currency than the one set in model definition.

What do you think? Thanks!

Updated_fields on MoneyField not working

On model I have fields:

    currency = models.CharField(max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH)
    price_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    price = MoneyField(amount_field="price_amount", currency_field="currency")

When I change price on objects and try to save it with object.save(update_fields=["price"]). My object is not updated and I haven't got any error.

Fails when using null=True and NULL values.

Got this traceback when I set a None value on a nullable column.

File "/home/ubuntu/.virtualenvs/doterrapro/local/lib/python2.7/site-packages/django_prices/models.py" in get_db_prep_save
  25.         return connection.ops.value_to_db_decimal(value.net,

Exception Type: AttributeError at /admin/marketplace/product/7/
Exception Value: 'NoneType' object has no attribute 'net'

How to install?

I tried to install prices_django like an APP (copy folder to my project, register in INSTALLED_APPS).

But I don't understand this import, which I can find in moduls.py, forms.py and widgets.py:
from prices import Price

And I'll always get an an ImportError: No module named prices. And if I rename prices_django to prices I get: cannot import name Price

What is Price anyway? I didn't find a class called like that

Support For DRF??

Any plans to support Django REST Framework? As I am heavily invested in this library already, now I need a couple of serializations for DRF...

Passing a widget instance to MoneyField produce crash

For example, when doing:

field = MoneyField(widget=MyWidget())

Would produce:

  File "/Users/mikail/Development/saleor/saleor/urls.py", line 14, in <module>
    from .dashboard.urls import urlpatterns as dashboard_urls
  File "/Users/mikail/Development/saleor/saleor/dashboard/urls.py", line 8, in <module>
    from .discount.urls import urlpatterns as discount_urls
  File "/Users/mikail/Development/saleor/saleor/dashboard/discount/urls.py", line 3, in <module>
    from . import views
  File "/Users/mikail/Development/saleor/saleor/dashboard/discount/views.py", line 15, in <module>
    from . import forms
  File "/Users/mikail/Development/saleor/saleor/dashboard/discount/forms.py", line 28, in <module>
    (currency, currency) for currency in settings.AVAILABLE_CURRENCIES
  File "/Users/mikail/Development/saleor-venv/lib/python3.7/site-packages/django_prices/forms.py", line 49, in __init__
    fields, widget=widget_instance, *args, **kwargs
UnboundLocalError: local variable 'widget_instance' referenced before assignment

Taxes are not being saved

Whereas Gross and Taxes can be changed within the Price, the changes are not being saved in the model object.

>>> from prices import Price, LinearTax, inspect_price
>>> p = Price('1.99')
>>> p += Price(50)
>>> p |= LinearTax('0.23', '23% VAT')
>>> p
Price(net='51.99', gross='63.9477', currency=None)

>>> product = Product.objects.create(name="Asd", price=p)
>>> product.price.gross
Decimal('63.9477')

>>> Product.objects.last().price.gross
Decimal('51.99')
>>> Product.objects.last().price.tax
Decimal('0.00')

In the django-prices.models I can see that only net value is saved to db. Please confirm if that is the case and let me know how other values can be added. I need to have taxes in the store and I don't want to redo the whole product->cart->checkout->order stack.

Introduce snapshottests

DoD:

  • add snapshot pkg to dev requirements
  • update CI to not generate snapshots
  • update tests like test_money_input_widget_renders and test_non_existing_locale

Write tests involving models with TaxedMoneyField and save()/delete() calls

During work on updating Saleor's codebase to use current prices, I've ran into plenty of edge cases with Django ORM accessing different attributes on TaxedMoneyField instances when running different operations.

Those could've been found much sooner if our test suite included following scenarios:

  1. model.save()
  2. model.save(update_fields)
  3. model.delete()

We should also explore how behavior of those changes for models that are:

  1. pointed to by ForeignKey in other model
  2. are pointing to other model via ForeignKey

This is because Django's ORM appears to look up related models fields, iterating on them and snooping out different attrs for its checks and validations.

Decimal and int can be saved to MoneyFields

Decimal and Ints are accepted value for MoneyField, which might lead to hard-to-catch bugs. I'd propose to only accept Money types, except for 0 value - which can be automatically parsed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.