├── .gitignore ├── CHANGELOG.txt ├── DESCRIPTION ├── LICENSE ├── README.rst ├── django_bnr ├── __init__.py ├── admin.py ├── app.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── get_bnr_rate.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py └── utils.py ├── manage.py ├── requirements.txt ├── settings.py ├── setup.py └── urls.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | 58 | db.sqlite 59 | settings_local.py 60 | 61 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | === 0.4.0 === 2 | The request to fetch data is now made via https, to the right domain: bnr.ro 3 | 4 | === 0.3.0 === 5 | Added support for Django 1.11 6 | Removed South migrations 7 | 8 | === 0.2.0 === 9 | Added support for south migrations for backward compatibility 10 | 11 | === 0.1.0 === 12 | Initial release 13 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | BNR exchange rates for django apps. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 PressLabs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-bnr 2 | ========== 3 | 4 | BNR exchange rates for django apps. 5 | 6 | Installation 7 | ------------ 8 | 9 | To get the latest stable release from PyPi 10 | 11 | .. code-block:: bash 12 | 13 | pip install django-bnr 14 | 15 | To get the latest commit from GitHub 16 | 17 | .. code-block:: bash 18 | 19 | pip install -e git+git://github.com/PressLabs/django-bnr.git#egg=django_bnr 20 | 21 | Add ``django_bnr`` to your ``INSTALLED_APPS`` 22 | 23 | .. code-block:: python 24 | 25 | INSTALLED_APPS = ( 26 | ..., 27 | 'django_bnr', 28 | ) 29 | 30 | Don't forget to migrate your database 31 | 32 | .. code-block:: bash 33 | 34 | ./manage.py migrate django_bnr 35 | 36 | 37 | Usage 38 | ----- 39 | 40 | The package provides a python function for getting BNR exchange rates. 41 | 42 | .. code-block:: python 43 | 44 | from django_bnr.utils import get_bnr_rate 45 | get_bnr_rate(date, currency) 46 | 47 | It also provides a management command for getting the BNR rates. Running it 48 | daily in a cron ensures you have data cached since BNR offers rates for up to 49 | 15 days. 50 | 51 | .. code-block:: bash 52 | 53 | ./manage.py get_bnr_rate -c CURRENCY -d DATE 54 | 55 | 56 | Contribute 57 | ---------- 58 | 59 | If you want to contribute to this project, please perform the following steps 60 | 61 | .. code-block:: bash 62 | 63 | # Fork this repository 64 | # Clone your fork 65 | mkvirtualenv -p python2.7 django-bnr 66 | 67 | git co -b feature_branch master 68 | # Implement your feature and tests 69 | git add . && git commit 70 | git push -u origin feature_branch 71 | # Send us a pull request for your feature branch 72 | -------------------------------------------------------------------------------- /django_bnr/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.4' 2 | default_app_config = 'django_bnr.app.DjangoBNRApp' 3 | -------------------------------------------------------------------------------- /django_bnr/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django_bnr.models import Rate 3 | 4 | 5 | # Register your models here. 6 | class RateAdmin(admin.ModelAdmin): 7 | list_display = ('rate', 'currency', 'date') 8 | 9 | 10 | admin.site.register(Rate, RateAdmin) 11 | -------------------------------------------------------------------------------- /django_bnr/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: ft=python:sw=4:ts=4:sts=4:et: 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class DjangoBNRApp(AppConfig): 8 | name = 'django_bnr' 9 | verbose_name = 'BNR Exchange Rates' 10 | -------------------------------------------------------------------------------- /django_bnr/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/presslabs/django-bnr/07ed65ba8e153197862baa8a4428e068ade99c9e/django_bnr/management/__init__.py -------------------------------------------------------------------------------- /django_bnr/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/presslabs/django-bnr/07ed65ba8e153197862baa8a4428e068ade99c9e/django_bnr/management/commands/__init__.py -------------------------------------------------------------------------------- /django_bnr/management/commands/get_bnr_rate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: ft=python:sw=4:ts=4:sts=4:et: 3 | import datetime 4 | import decimal 5 | 6 | from optparse import make_option 7 | 8 | from django.core.management.base import BaseCommand 9 | from django_bnr.utils import get_bnr_rate 10 | from django_bnr.models import CURRENCIES 11 | 12 | 13 | class Command(BaseCommand): 14 | help = 'Gets the BNR rate for given currency' 15 | 16 | def add_arguments(self, parser): 17 | parser.add_argument( 18 | "-c", 19 | "--currency", 20 | type=str, 21 | choices=CURRENCIES, 22 | dest="currency", 23 | action="store", 24 | default="USD", 25 | help="Currency to get rate for", 26 | ) 27 | 28 | # Named (optional) arguments 29 | parser.add_argument( 30 | "-d", 31 | "--date", 32 | type=str, 33 | dest="date", 34 | action="store", 35 | default=datetime.date.today().strftime("%Y-%m-%d"), 36 | help="Date to get rate for", 37 | ) 38 | 39 | def handle(self, *args, **options): 40 | currency = options['currency'] 41 | date = datetime.datetime.strptime(options['date'], '%Y-%m-%d').date() 42 | print(get_bnr_rate(date, currency)) 43 | -------------------------------------------------------------------------------- /django_bnr/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Rate', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('rate', models.DecimalField(null=True, verbose_name=b'Exchange rate', max_digits=8, decimal_places=4, blank=True)), 18 | ('date', models.DateField(db_index=True)), 19 | ('currency', models.CharField(default=b'USD', max_length=3, db_index=True, choices=[(b'CHF', b'CHF'), (b'EUR', b'EUR'), (b'GBP', b'GBP'), (b'USD', b'USD')])), 20 | ], 21 | options={ 22 | 'ordering': ['-date', 'currency'], 23 | }, 24 | bases=(models.Model,), 25 | ), 26 | migrations.AlterUniqueTogether( 27 | name='rate', 28 | unique_together=set([('date', 'currency')]), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /django_bnr/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/presslabs/django-bnr/07ed65ba8e153197862baa8a4428e068ade99c9e/django_bnr/migrations/__init__.py -------------------------------------------------------------------------------- /django_bnr/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | CURRENCIES = ('USD', 'EUR', 'CHF', 'GBP') 4 | 5 | 6 | class Rate(models.Model): 7 | rate = models.DecimalField(max_digits=8, decimal_places=4, null=True, 8 | blank=True, verbose_name=('Exchange rate')) 9 | date = models.DateField(db_index=True) 10 | currency = models.CharField(choices=((c, c) for c in sorted(CURRENCIES)), 11 | max_length=3, db_index=True, default='USD') 12 | 13 | class Meta: 14 | ordering = ['-date', 'currency'] 15 | unique_together = ('date', 'currency') 16 | -------------------------------------------------------------------------------- /django_bnr/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: ft=python:sw=4:ts=4:sts=4:et: 3 | import requests 4 | import decimal 5 | 6 | from datetime import timedelta 7 | from xml.etree import ElementTree 8 | 9 | from django_bnr.models import Rate 10 | 11 | 12 | def get_bnr_rate(date, currency='USD'): 13 | try: 14 | rate = Rate.objects.get(date=date, currency=currency) 15 | return rate.rate 16 | except Rate.DoesNotExist: 17 | d = date.strftime('%Y-%m-%d') 18 | r = requests.get('https://www.bnr.ro/nbrfxrates10days.xml') 19 | r.raise_for_status() 20 | rate = None 21 | days = 0 22 | xpath_fmt = ("./{xsd}Body/{xsd}Cube[@date='{date}']/" 23 | "{xsd}Rate[@currency='{currency}']") 24 | while rate is None: 25 | rate = ElementTree.fromstring(r.text).find(xpath_fmt.format( 26 | xsd='{http://www.bnr.ro/xsd}', 27 | date=d, 28 | currency=currency 29 | )) 30 | if rate is None: 31 | days += 1 32 | if days == 7: 33 | raise RuntimeError('Cannot get exchange rate for ' 34 | '%(currency)s from %(date)s' % { 35 | 'currency': currency, 36 | 'date': date 37 | }) 38 | d = (date - timedelta(days=days)).strftime('%Y-%m-%d') 39 | 40 | rate = decimal.Decimal(rate.text) 41 | try: 42 | Rate.objects.create(date=date, currency=currency, rate=rate) 43 | except: 44 | pass 45 | return rate 46 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | if os.path.isfile(os.path.join(os.path.dirname(__file__), 7 | 'settings_local.py')): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings_local') 9 | else: 10 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') 11 | 12 | from django.core.management import execute_from_command_line 13 | 14 | execute_from_command_line(sys.argv) 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | django 3 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | These settings are used by the ``manage.py`` command. 3 | 4 | """ 5 | import os 6 | 7 | DEBUG = True 8 | 9 | SITE_ID = 1 10 | 11 | USE_TZ = True 12 | TIME_ZONE = 'UTC' 13 | 14 | DATABASES = { 15 | 'default': { 16 | 'ENGINE': 'django.db.backends.sqlite3', 17 | 'NAME': 'db.sqlite', 18 | } 19 | } 20 | 21 | INSTALLED_APPS = [ 22 | # Django core apps 23 | 'django.contrib.admin', 24 | 'django.contrib.admindocs', 25 | 'django.contrib.auth', 26 | 'django.contrib.contenttypes', 27 | 'django.contrib.messages', 28 | 'django.contrib.sessions', 29 | 'django.contrib.staticfiles', 30 | 31 | 'django_bnr' 32 | ] 33 | 34 | 35 | ROOT_URLCONF = 'urls' 36 | PROJECT_ROOT = os.path.dirname(__file__) 37 | STATIC_URL = '/static/' 38 | 39 | MIDDLEWARE_CLASSES = ( 40 | 'django.contrib.sessions.middleware.SessionMiddleware', 41 | 'django.middleware.common.CommonMiddleware', 42 | 'django.middleware.csrf.CsrfViewMiddleware', 43 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 44 | 'django.contrib.messages.middleware.MessageMiddleware', 45 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 46 | ) 47 | 48 | SECRET_KEY = 'secret' 49 | 50 | from django.utils.log import DEFAULT_LOGGING as LOGGING 51 | 52 | LOGGING['loggers']['django'] = { 53 | 'level': 'DEBUG', 54 | 'handlers': ['console'] 55 | } 56 | 57 | LOGGING['loggers']['django.security'] = { 58 | 'level': 'DEBUG', 59 | 'handlers': ['console'] 60 | } 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Python setup file for the bnr app. 4 | 5 | In order to register your app at pypi.python.org, create an account at 6 | pypi.python.org and login, then register your new app like so: 7 | 8 | python setup.py register 9 | 10 | If your name is still free, you can now make your first release but first you 11 | should check if you are uploading the correct files: 12 | 13 | python setup.py sdist 14 | 15 | Inspect the output thoroughly. There shouldn't be any temp files and if your 16 | app includes staticfiles or templates, make sure that they appear in the list. 17 | If something is wrong, you need to edit MANIFEST.in and run the command again. 18 | 19 | If all looks good, you can make your first release: 20 | 21 | python setup.py sdist upload 22 | 23 | For new releases, you need to bump the version number in 24 | django_bnr/__init__.py and re-run the above command. 25 | 26 | For more information on creating source distributions, see 27 | http://docs.python.org/2/distutils/sourcedist.html 28 | 29 | """ 30 | import os 31 | from setuptools import setup, find_packages 32 | import django_bnr as app 33 | 34 | 35 | install_requires = ( 36 | 'requests', 37 | ) 38 | 39 | 40 | def read(fname): 41 | try: 42 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 43 | except IOError: 44 | return '' 45 | 46 | setup( 47 | name="django-bnr", 48 | version=app.__version__, 49 | description=read('DESCRIPTION'), 50 | long_description=read('README.rst'), 51 | license='The MIT License', 52 | platforms=['OS Independent'], 53 | install_requires=install_requires, 54 | keywords='django, app, reusable, exchange rates', 55 | author='Presslabs', 56 | author_email='ping@presslabs.com', 57 | url="https://github.com/PressLabs/django-bnr", 58 | packages=find_packages(), 59 | include_package_data=True, 60 | ) 61 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | """URLs for the django-bnr app.""" 2 | 3 | from django.conf.urls import url 4 | from django.contrib import admin 5 | 6 | 7 | admin.autodiscover() 8 | 9 | urlpatterns = [ 10 | url(r'^admin/', admin.site.urls), 11 | ] 12 | --------------------------------------------------------------------------------