Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/5monkeys/django-bananas
Django extensions the monkey way
https://github.com/5monkeys/django-bananas
django python
Last synced: 4 days ago
JSON representation
Django extensions the monkey way
- Host: GitHub
- URL: https://github.com/5monkeys/django-bananas
- Owner: 5monkeys
- License: mit
- Created: 2015-03-30T13:42:17.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2023-10-19T10:24:07.000Z (about 1 year ago)
- Last Synced: 2024-12-27T07:08:05.519Z (11 days ago)
- Topics: django, python
- Language: Python
- Size: 520 KB
- Stars: 65
- Watchers: 10
- Forks: 17
- Open Issues: 8
-
Metadata Files:
- Readme: README.rst
- License: LICENSE
- Authors: AUTHORS
Awesome Lists containing this project
README
================================================================================
:banana: Django Bananas - Django extensions the monkey way
================================================================================.. image:: https://github.com/5monkeys/django-bananas/workflows/CI/badge.svg
:target: https://github.com/5monkeys/django-bananas/actions.. image:: https://img.shields.io/pypi/v/django-bananas.svg
:target: https://pypi.python.org/pypi/django-bananas/--------------------------------------------------------------------------------
Install
--------------------------------------------------------------------------------django-bananas is on PyPI, so just run:
.. code-block:: bash
python3 -m pip install django-bananas
Using DRF specific features like Bananas Admin and fencing requires
djangorestframework and drf-yasg and it's recommended to install django-bananas
with the ``drf`` extra to keep those in sync:.. code-block:: bash
python3 -m pip install django-bananas[drf]
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Compatibility
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Currently tested only for
- Django 3.2 under Python 3.7-3.9
- Django 4.0 under Python 3.8-3.10
- Django 4.1 under Python 3.8-3.10
- Django 4.2 under Python 3.8-3.10Pull requests welcome!
--------------------------------------------------------------------------------
Examples
--------------------------------------------------------------------------------++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Models
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++TimeStampedModel
================================================================================Abstract ``TimeStampedModel`` with date created/modified fields:
Use TimeStampedModel as base class for your model
.. code-block:: py
from bananas.models import TimeStampedModel
class Book(TimeStampedModel):
passthe timestamps can be accessed on the model as
.. code-block:: pycon
>>> book.date_created
>>> book.date_modifiedUUIDModel
================================================================================Abstract model that uses a Django 1.8 UUID field as the primary key.
.. code-block:: py
from bananas.models import UUIDModel
class User(UUIDModel):
display_name = models.CharField(max_length=255)
email = models.EmailField().. code-block:: pycon
>>> user.id
UUID('70cf1f46-2c79-4fc9-8cc8-523d67484182')>>> user.pk
UUID('70cf1f46-2c79-4fc9-8cc8-523d67484182')SecretField
================================================================================Can be used to generate and store "safe" random bytes for authentication.
.. code-block:: py
from bananas.models import SecretField
class User(models.Model):
# Ask for 32 bytes and require 24 bytes from urandom
token = SecretField(num_bytes=32, min_bytes=24).. code-block:: pycon
>>> User.objects.create() # Token is generated automatically
>>> user.token
'3076f884da827809e80ced236e8da20fa36d0c27dd036bdd4afbac34807e5cf1'URLSecretField
================================================================================An implementation of SecretField that generates an URL-safe base64 string
instead of a hex representation of the random bytes... code-block:: py
from bananas.models import URLSecretField
class User(models.Model):
# Generates an URL-safe base64 representation of the random value
token = URLSecretField(num_bytes=32, min_bytes=24).. code-block:: pycon
>>> user.token
'WOgrNwqFKOF_LsHorJy_hGpPepjvVH7Uar-4Z_K6DzU-'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ORM
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++New ``queryset.dicts()`` with field renaming through kwargs, and `dot-dict`
style results:.. code-block:: py
from bananas.query import ExtendedQuerySet
class Book(TimeStampedModel):
author = ForeignKey(Author)
objects = Manager.from_queryset(ExtendedQuerySet)().. code-block:: pycon
>>> book = Book.objects.dicts("id", author="author__name").first()
{'id': 1, 'author': 'Jonas'}
>>> book.author
'Jonas'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admin
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Custom django admin stylesheet.
.. warning:: Work in progress. Only a few views styled completely as of now.
.. code-block:: py
# settings.py
INSTALLED_APPS = (
"bananas", # Needs to be before "django.contrib.admin"
"django.contrib.admin",
...,
)ADMIN = {
"SITE_HEADER": "Bananas",
"SITE_TITLE": "Bananas Admin",
"INDEX_TITLE": "Admin Panel",
# 'BACKGROUND_COLOR': '#363c3f',
}.. code-block:: py
# your main urls.py
from bananas import adminurlpatterns = [
# ...
url(r"^admin/", include(admin.site.urls)),
].. code-block:: py
# app/admin.py or something
from django.conf.urls import url
from bananas import admin@admin.register
class MyAdminView(admin.AdminView):
def get_urls(self):
return [
url(r"^custom/$", self.admin_view(self.custom_view)),
# ^^ Note that the view is wrapped in self.admin_view.
# Needed for permissions and to prevent any
# threading issues.
]def get(self, request):
return self.render("admin/template.html", {})def custom_view(self, request):
return self.render("admin/custom.html", {})++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admin API
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Django admin API for use with `django-bananas.js
`_ (react admin site). This
feature requires installation with the ``drf`` extra... code-block:: py
# app/admin.py or something
from bananas.admin.api.mixins import BananasAPI
from bananas.admin.api.schemas import schema
from bananas.admin.api.views import BananasAdminAPI
from bananas.lazy import lazy_title
from django.utils.translation import gettext_lazy as _
from rest_framework import viewsetsclass CustomAdminAPI(BananasAdminAPI):
name = lazy_title(_("custom"))@schema(query_serializer=SomeSerializer, responses={200: SomeSerializer})
def list(self, request):
return ...class SomeModelAdminAPI(BananasAPI, viewsets.ModelViewSet):
serializer_class = SomeModelSerializerdef list(self, request):
return ..... code-block:: py
# app/urls.py or something
from bananas.admin import api
from django.conf.urls import include, pathfrom .admin import CustomAdminAPI, SomeModelAdminAPI
api.register(CustomAdminAPI)
api.register(SomeModelAdminAPI)urlpatterns = [
path(r"^api/", include("bananas.admin.api.urls")),
].. code-block:: py
# setting.py
ADMIN = {
"API": {
# Optional: override the default OpenAPI schemes
"SCHEMES": ["https"],
}
}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Database URLs
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Parse database information from a URL, kind of like SQLAlchemy.
Engines
================================================================================Currently supported engines are:
============================== ===========================================
URI scheme Engine
============================== ===========================================
pgsql, postgres, postgresql django.db.backends.postgresql_psycopg2
mysql django.db.backends.mysql
oracle django.db.backends.oracle
sqlite, sqlite3 django.db.backends.sqlite3
mysqlgis django.contrib.gis.db.backends.mysql
oraclegis django.contrib.gis.db.backends.oracle
postgis django.contrib.gis.db.backends.postgis
spatialite django.contrib.gis.db.backends.spatialite
============================== ===========================================You can add your own by running ``register(scheme, module_name)`` before parsing.
database_conf_from_url(url)
Return a django-style database configuration based on ``url``.:param url: Database URL
:return: Django-style database configuration dictExample:
.. code-block:: pycon
>>> from bananas.url import database_conf_from_url
>>> conf = database_conf_from_url(
... "pgsql://joar:[email protected]:4242/tweets/tweetschema?hello=world"
... )
>>> sorted(conf.items()) # doctest: +NORMALIZE_WHITESPACE
[('ENGINE', 'django.db.backends.postgresql_psycopg2'),
('HOST', '5monkeys.se'),
('NAME', 'tweets'),
('PARAMS', {'hello': 'world'}),
('PASSWORD', 'hunter2'),
('PORT', 4242),
('SCHEMA', 'tweetschema'),
('USER', 'joar')]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bananas.environment - Helpers to get setting values from environment variables
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++``bananas.environment.env`` is a wrapper around ``os.environ``, it provides the
standard ``.get(key, value)``, method to get a value for a key, or a default if
the key is not set - by default that default is ``None`` as you would expect.
What is more useful is the additional type-parsing ``.get_*`` methods it
provides:- ``get_bool``
- ``get_int``
- ``get_list``, ``get_set``, ``get_tuple``:get_int:
.. code-block:: pycon
>>> # env ONE=1
>>> env.get_int("ONE")
1
>>> env.get_int("TWO") # Not set
None
>>> env.get_int("TWO", -1) # Not set, default to -1
-1:get_bool:
returns ``True`` if the environment variable value is any of,
case-insensitive:- ``"true"``
- ``"yes"``
- ``"on"``
- ``"1"``returns ``False`` if the environment variable value is any of,
case-insensitive:- ``"false"``
- ``"no"``
- ``"off"``
- ``"0"``if the value is set to anything other than above, the default value will be returned instead.
e.g.:
.. code-block:: pycon
>>> # env CAN_DO=1 NO_THANKS=false NO_HABLA=f4lse
>>> env.get_bool("CAN_DO")
True
>>> env.get_bool("NO_THANKS")
False
>>> env.get_bool("NO_HABLA") # Set, but not valid
None
>>> env.get_bool("NO_HABLA", True) # Set, but not valid, with default
True
>>> env.get_bool("IS_NONE") # Not set
None
>>> env.get_bool("IS_NONE", False) # Not set, default provided
False:get_tuple, get_list, get_set:
Returns a ``tuple``, ``list`` or ``set`` of the environment variable string,
split by the ascii comma character. e.g.:.. code-block:: pycon
>>> # env FOOS=foo,foo,bar
>>> get_list("FOO")
['foo', 'foo', 'bar']
>>> get_set("FOO")
set(['foo', 'bar'])++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bananas.secrets - Helpers for getting secrets from files
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Is useful for getting the content of secrets stored in files. One usecase is `docker secrets
`_.``BANANAS_SECRETS_DIR`` can be used to configure the directory that secrets live in. Defaults to ``/run/secrets/``.
.. code-block:: pycon
>>> from bananas import secrets
>>> secrets.get_secret("hemlis")
"topsecret"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bananas.drf.fencing - Fence DRF views with HTTP conditional headers
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Building blocks for composing HTTP conditionals to guard DRF views. Built to
work well in conjunction with ``BananasAdminAPI`` and ``TimeStampedModel``. This
feature requires installation with the ``drf`` extra.Fences add a header parameter to the exposed OpenAPI schema if you're using
drf-yasg.``allow_if_unmodified_since``
=============================Make a view-set for a ``TimeStampedModel`` only accept updates when
``If-Unmodified-Since`` specifies a date before the ``date_modified`` of the
updated instance.Due to comparing datetime instances, using ``allow_if_unmodified_since``
requires running Django with timezone support enabled, ``USE_TZ = TRUE``... code-block:: python
from bananas.drf.fencing import FencedUpdateModelMixin, allow_if_unmodified_since
class ItemAPI(FencedUpdateModelMixin, GenericViewSet):
fence = allow_if_unmodified_since()
serializer_class = ItemSerializer``allow_if_match``
==================Make a view-set that requires passing a version string in ``If-Match`` and
rejects requests when the given version does not match the ``version`` attribute
of the updated instance... code-block:: python
from bananas.drf.fencing import FencedUpdateModelMixin, allow_if_match
class ItemAPI(FencedUpdateModelMixin, GenericViewSet):
fence = allow_if_match(operator.attrgetter("version"))
serializer_class = ItemSerializer``Fence``
=========Example implementing a fence for ``If-Modified-Since``:
.. code-block:: python
import operator
from drf_yasg import openapi
from rest_framework import status
from rest_framework.exceptions import APIException
from bananas.drf.fencing import Fence, header_date_parser, parse_date_modifiedclass NotModified(APIException):
status_code = status.HTTP_304_NOT_MODIFIED
default_detail = "An HTTP precondition failed"
default_code = "not_modified"allow_if_not_modified_since = Fence(
get_token=header_date_parser("If-Modified-Since"),
compare=operator.gt,
get_version=parse_date_modified,
openapi_parameter=openapi.Parameter(
in_=openapi.IN_HEADER,
name="If-Modified-Since",
type=openapi.TYPE_STRING,
required=True,
description=(
"Time of last edit of the client's representation of the resource in "
"RFC7231 format."
),
),
rejection=NotModified("The resource is unmodified"),
)++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Contributing
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Contributing is welcome in the form of PRs and issues. If you want to add a
bigger feature or contribute with a large change in current behaviour it's
always a good idea to start a discussion with an issue before getting started.New additions will be expected to have 100% test coverage as well as type hints
and documentation to be considered to be merged.Development
===========Testing and development requirements can be installed using package extras
``test`` and ``dev`` respectively. You'll most likely always want to install the
``drf`` extra when installing ``dev``.To get started, setup a virtualenv and then install test requirements and run
tests and checks on Python 3.9/Django 3.1 with:.. code-block:: bash
python3 -m pip install -e .[test]
TOXENV=py39-django31,checks python3 -m toxYou can install development requirements into your virtualenv. Linting and
formatting uses pre-commit which you could also install on a system level... code-block:: bash
python3 -m pip install -e .[dev,drf]
make type-check
pre-commit run --all-filesAfter installing pre-commit, you can enable hooks to have it run before you
publish pull requests... code-block:: bash
pre-commit install -t pre-push
After installing ``dev`` you can also run tests without tox for rapid iteration
and select specific tests with the ``test`` argument to ``make test``:.. code-block:: bash
make test test='-k test_logout'