Giter Club home page Giter Club logo

django-candv-choices's Introduction

django-candv-choices

Version of PyPI package Number of downloads of PyPI package Supported versions of Python Package license

Use complex constants built with candv library instead of standard choices fields for Django models.

Try online live demo! Use demo/demo as login/pass for authentication.

Live demo screenshot

Table of contents

Installation

Install from PyPI:

$ pip install django-candv-choices

Problem overview

Well, you need to define some constant choices for your Django model field. Let's start from defining constants themselves:

# constants.py
from django.utils.translation import ugettext_lazy as _

AUTH_TYPE_BASIC = 'BASIC'
AUTH_TYPE_DIGEST = 'DIGEST'
AUTH_TYPE_CLIENT_CERT = 'CLIENT-CERT'
AUTH_TYPE_FORM = 'FORM'

AUTH_TYPES = (
    (AUTH_TYPE_BASIC, _("HTTP Basic Authentication")),
    (AUTH_TYPE_DIGEST, _("HTTP Digest Authentication ")),
    (AUTH_TYPE_CLIENT_CERT, _("HTTPS Client Authentication ")),
    (AUTH_TYPE_FORM, _("Form Based Authentication ")),
)

Here we define constant names and attach verbose names to them. Bloated definition, no docstring for constants group, no docstings per constant. What if you need to define some help text per constant? 4 more definitions? Well, then just imagine, how you will attach them. And what about other attributes? And what about adding some methods for constants? How about getting constant by its name? By value? And how about performing some operations on the whole constants group?

Only at this point you may end up with one big module which will work only with one group of constants. And this work will be a big pain.

But OK, let's go further and define some DB model field:

# models.py
from django.db import models
from django.utils.translation import ugettext_lazy as _

from . constants import AUTH_TYPES, AUTH_TYPE_BASIC


class Request(models.Model):

    auth_type = models.CharField(
        verbose_name=_("Auth type"),
        help_text=_("Example of default constants"),
        choices=AUTH_TYPES,
        blank=False,
        max_length=11,
        default=AUTH_TYPE_BASIC)

3 things to mention here:

  • you have to import constant group itself;
  • you may have to import dafault value too;
  • you need go back to constants definition, iterate over each constant, calculate its length and select the longest value to pass it as max_length argument. And don't try to make a mistake, or you will be punished otherwise.

I use CharField here intentionally. It can be good to use IntegerField, PositiveSmallIntegerField and so on, but it is very probable that you will be willing someone to kill you due to hidden bugs.

Now it's showtime! Let's render our field:

<ul>
{% for r in requests %}
  <li>{{ r.auth_type }}</li>
{% endfor %}
</ul>

What do you see? BASIC, DIGEST, FORM, etc. Oops! How to get our human messages like HTTP Basic Authentication?

You need to convert constants group to dict and pass it to template's context! But wait, this is not the end. You can not access dict values directly within templates. You need to create a library of template tags, register a filter and load the library to template:

# templatetags/custom_tags.py
from django import template

register = template.Library()


@register.filter
def lookup(d, key):
    return d[key]
{% load custom_tags %}
<ul>
{% for r in requests %}
  <li>{{ AUTH_TYPES|lookup:r.auth_type }}</li>
{% endfor %}
</ul>

This is madness!

Solution

The solution is to use candv and this library. The former allows you to define stand-alone groups of complex constants and latter allows you to use those constants as choises.

Let's examine some simple example and define some constants:

# constants.py
from candv import SimpleConstant, Constants

class METHOD_TYPE(Constants):
    """
    Available HTTP methods.
    """
    GET = SimpleConstant()
    PUT = SimpleConstant()
    POST = SimpleConstant()
    DELETE = SimpleConstant()
    TRACE = SimpleConstant()

Here we defined a group of constants with no attributes. Looks pretty, let's use it:

# models.py
from candv_x.django.choices import ChoicesField

from django.db import models
from django.utils.translation import ugettext_lazy as _

from . constants import METHOD_TYPE

class Request(models.Model):

    method = ChoicesField(
        verbose_name=_("method"),
        help_text=_("Example of simple candv constants"),
        choices=METHOD_TYPE,
        blank=False,
    )

That's all. You can pass some default value if you want, e.g. default=METHOD_TYPE.GET.

Now you can render it:

<ul>
{% for r in requests %}
  <li>{{ r.method.name }}</li>
{% endfor %}
</ul>

The output will contain GET, PUT, POST, etc. Want more? Let's add values, verbose names and help texts:

# constants.py
from candv import VerboseValueConstant, Values
from django.utils.translation import ugettext_lazy as _

class RESULT_TYPE(Values):
    """
    Possible operation results.
    """
    SUCCESS = VerboseValueConstant(
        value='2C7517',
        verbose_name=_("Success"),
        help_text=_("Yay! Everything is good!")
    )
    FAILURE = VerboseValueConstant(
        value='A30D0D',
        verbose_name=_("Failure"),
        help_text=_("Oops! Something went wrong!")
    )
    PENDING = VerboseValueConstant(
        value='E09F26',
        verbose_name=_("Pending"),
        help_text=_("Still waiting for the task to complete...")
    )

Here we have used Values as container and VerboseValueConstant as class for items. Each constant has a name (e.g. SUCCESS), a value, a verbose text and a help text. All of this you can access directly from everywhere.

Field definition does not differ much from previous:

# models.py
from candv_x.django.choices import ChoicesField

from django.db import models
from django.utils.translation import ugettext_lazy as _

from . constants import RESULT_TYPE

class Request(models.Model):

    result = ChoicesField(
            verbose_name=_("result"),
            help_text=_("Example of complex candv constants with verbose names, "
                        "help texts and inner values"),
            choices=RESULT_TYPE,
            blank=False,
            default=RESULT_TYPE.SUCCESS,
        )

You may use blank=True if you wish, there's no problem. Let's output our data:

<table>
{% for r in requests %}
  <tr>
    <td style="color: #{{ r.result.value }};" title="{{ r.result.help_text }}">
      {{ r.result.verbose_name }}
    </td>
  </tr>
{% endfor %}
</table>

Not so hard, innit?

You can pass any constants to ChoicesField from your old projects or external libraries. Enjoy!

Caveats

  • Django admin renders choices by converting them to strings. So, __str__ and __unicode__ methods will be automatically overriden for constant items. It will return the name of the constant. By default, constants in candv do not have those methods at all (I cannot find a reason why the should to), so it seems not to be a problem. Just be aware.
  • candv supports creating hierarchies of constants. If you have some reason to use them as choices for DB field, take into accout that choices will be built only from top-level group of constants.

Things to think about

  • Django has MultipleChoiceField and TypedMultipleChoiceField. I haven't used used them, but I think it can be useful to implement analogues for 'candv', especially for MultipleChoiceField.
  • I think, there is a place to think about implementation of full support of hierarchies. Maybe it's possible to make some nested choices, or at least flatten them.

Changelog

You can click a version name to see a diff with the previous one.

  • 1.1.5 (Aug 1, 2015)
  • 1.1.4 (Jul 2, 2015)
    • Add support for Python 3 (issue #6).
    • Add support for migrations in Django >= 1.7 (issue #7).
    • Imports which will become deprecated in Django 1.9 are not used now.
  • 1.1.3 (Oct 11, 2014)
    • candv dependency updated up to v1.2.0.
    • Add support for South (issue #4).
    • Choices' form field can display help text as option's title now (issue #1).
  • 1.1.0 (Jul 19, 2014)
    • rename package to choices and move into candv_x.django (x stands for extensions)
  • 1.0.0 (Jun 22, 2014)

    Initial version

django-candv-choices's People

Contributors

o3bvv avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

pombredanne

django-candv-choices's Issues

Python 3 support

types.MethodType accepts only 2 arguments in Python 3, causing existing code to crash like this:

 File "C:\Python34\lib\site-packages\candv_x\django\choices\db.py", line 30, in __init__
   self._patch_items(choices)
 File "C:\Python34\lib\site-packages\candv_x\django\choices\db.py", line 45, in _patch_items
   method = MethodType(_to_string, constant, constant.__class__)
TypeError: method expected 2 arguments, got 3

Hierarchies support

Taken from Things to think about:

I think, there is a place to think about implementation of full support of hierarchies. Maybe it's possible to make some nested choices, or at least flatten them.

Ability to specify display attribute for constants

It can be useful to have ability to specify name of constant's attribute to display on UI.

By default, name is used. verbose_name is used for verbose constants.

There is going to be support of value for bare value constants (see #10).

It would be good to have an ability to explicitly specify attribute to use, e.g.:

from candv import Constants, ValueConstant


class USER_ROLES(Constants):
    ANALYST = ValueConstant('analyst')
    OPERATOR = ValueConstant('operator')

    __display_attribute__ = 'value'

Here, __display_attribute__ will tell to use value attribute for rendering constants.

This may become useful for custom constant classes which are unknown for library.

Display value of ValueConstant instead of its name on UI

When I use ValueConstant for choices, I want to display value rather name on UI.
For example:

from candv import Constants, ValueConstant


class USER_ROLES(Constants):
    ANALYST = ValueConstant('analyst')
    OPERATOR = ValueConstant('operator')

and in admin I see ANALYST instead of analyst
feature

Dynamic values

Provide ability to set dynamic values for constants (e.g. integrate with django-constance or whatsoever).

Django 1.7+ support

Model migrations from Django 1.7 and above do not work causing the following crash:

D:\PYTHON\Projects\rof-stats-backend>python manage.py makemigrations squads
C:\Python34\lib\importlib\_bootstrap.py:321: RemovedInDjango19Warning: The django.forms.util module has been renamed. Use django.forms.utils instead.
 return f(*args, **kwds)

Traceback (most recent call last):
 File "C:\Python34\lib\site-packages\django\db\migrations\state.py", line 348, in from_model
   fields.append((name, field_class(*args, **kwargs)))
 File "C:\Python34\lib\site-packages\candv_x\django\choices\db.py", line 22, in __init__
   assert issubclass(choices, ConstantsContainer)
TypeError: issubclass() arg 1 must be a class

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
 File "manage.py", line 12, in <module>
   execute_from_command_line(sys.argv)
 File "C:\Python34\lib\site-packages\django\core\management\__init__.py", line 338, in execute_from_command_line
   utility.execute()
 File "C:\Python34\lib\site-packages\django\core\management\__init__.py", line 330, in execute
   self.fetch_command(subcommand).run_from_argv(self.argv)
 File "C:\Python34\lib\site-packages\django\core\management\base.py", line 390, in run_from_argv
   self.execute(*args, **cmd_options)
 File "C:\Python34\lib\site-packages\django\core\management\base.py", line 441, in execute
   output = self.handle(*args, **options)
 File "C:\Python34\lib\site-packages\django\core\management\commands\makemigrations.py", line 99, in handle
   ProjectState.from_apps(apps),
 File "C:\Python34\lib\site-packages\django\db\migrations\state.py", line 178, in from_apps
   model_state = ModelState.from_model(model)
 File "C:\Python34\lib\site-packages\django\db\migrations\state.py", line 354, in from_model
   e,
TypeError: Couldn't reconstruct field coalition on squads.Squad: issubclass() arg 1 must be a class

This happens because for some reason __init__ method of the field is invoked multiple times for some reason. And 2nd it is given a list of constants from container.

Ability to use subset of choices

It may be cool to use only a subset of choices of instead of full container. In pseudocode:

container = (foo, bar, baz, qux, )
field = choices([container.foo, container.baz, ])

'makemigrations' in Django 1.7+ fails to serialize default value for choices

python manage.py makemigrations candv_choices_demo
Migrations for 'candv_choices_demo':
  0001_initial.py:
    - Create model Request
Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/core/management/__init__.py", line 330, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/core/management/base.py", line 390, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/core/management/base.py", line 441, in execute
    output = self.handle(*args, **options)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/core/management/commands/makemigrations.py", line 143, in handle
    self.write_migration_files(changes)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/core/management/commands/makemigrations.py", line 171, in write_migration_files
    migration_string = writer.as_string()
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/db/migrations/writer.py", line 166, in as_string
    operation_string, operation_imports = OperationWriter(operation).serialize()
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/db/migrations/writer.py", line 124, in serialize
    _write(arg_name, arg_value)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/db/migrations/writer.py", line 75, in _write
    arg_string, arg_imports = MigrationWriter.serialize(item)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/db/migrations/writer.py", line 303, in serialize
    item_string, item_imports = cls.serialize(item)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/db/migrations/writer.py", line 377, in serialize
    return cls.serialize_deconstructed(path, args, kwargs)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/db/migrations/writer.py", line 268, in serialize_deconstructed
    arg_string, arg_imports = cls.serialize(arg)
  File "/Users/alex/.virtualenvs/candv/lib/python2.7/site-packages/django/db/migrations/writer.py", line 465, in serialize
    "topics/migrations/#migration-serializing" % (value, get_docs_version())
ValueError: Cannot serialize: <constant 'RESULT_TYPE.SUCCESS'>
There are some values Django cannot serialize into migration files.
For more, see https://docs.djangoproject.com/en/1.8/topics/migrations/#migration-serializing

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.