Giter Club home page Giter Club logo

django-dynamic-preferences's Introduction

django-dynamic-preferences

image

image

image

Backers on Open Collective

Sponsors on Open Collective

Dynamic-preferences is a Django app, BSD-licensed, designed to help you manage your project settings. While most of the time, a settings.py file is sufficient, there are some situations where you need something more flexible such as:

  • per-user settings (or, generally speaking, per instance settings)
  • settings change without server restart

For per-instance settings, you could actually store them in some kind of profile model. However, it means that every time you want to add a new setting, you need to add a new column to the profile DB table. Not very efficient.

Dynamic-preferences allow you to register settings (a.k.a. preferences) in a declarative way. Preferences values are serialized before storage in database, and automatically deserialized when you need them.

With dynamic-preferences, you can update settings on the fly, through django's admin or custom forms, without restarting your application.

The project is tested and work under Python 3.7, 3.8, 3.9, 3.10 and 3.11 and with django 3.2 and 4.2.

Features

  • Simple to setup
  • Admin integration
  • Forms integration
  • Bundled with global and per-user preferences
  • Can be extended to other models if need (e.g. per-site preferences)
  • Integrates with django caching mechanisms to improve performance

Documentation

The full documentation is at https://django-dynamic-preferences.readthedocs.org.

Changelog

See https://django-dynamic-preferences.readthedocs.io/en/latest/history.html

Contributing

See https://django-dynamic-preferences.readthedocs.org/en/latest/contributing.html

Credits


Contributors


This project exists thanks to all the people who contribute!

image

Backers


Thank you to all our backers! Become a backer.

image

Sponsors


Support us by becoming a sponsor. Your logo will show up here with a link to your website. Become a sponsor.

image

django-dynamic-preferences's People

Contributors

agateblue avatar capaci avatar cleitondelima avatar czlee avatar eliotberriot avatar er-vin avatar eriktelepovsky avatar exequiel09 avatar hansegucker avatar jackatomenapps avatar jetuni avatar jitdev avatar jordiromera avatar jose-reveni avatar julienc91 avatar jwaschkau avatar mihalikv avatar mrzyrus avatar n3storm avatar natureshadow avatar neolithera avatar nourspace avatar oko-x avatar ondrejkolin avatar philipbelesky avatar ptrstn avatar ricard33 avatar thtomate avatar willseward avatar yurtaev 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

django-dynamic-preferences's Issues

PerInstancePreferenceModel Documentation

Thanks for the great utility library, we have moved all of our settings into it!

We are having trouble when subclassing PerInstancePreferenceRegistry, adding a suite of settings to specific model instances. This includes creating a registry and adding default settings for each instance.

If someone could point me in the right direction (or give a basic example) I'd be happy to write the documentation and add it as a pull request.

Cheers

ImportError: cannot import name global_preferences_registry

django-dynamic-preferences (0.8.2)

I am trying to run this project: https://github.com/mfomicheva/tradopad
And the following file fails: https://github.com/mfomicheva/tradopad/blob/master/rater/dynamic_preferences_registry.py

/Users/andy/tradopad/bin/python /Users/andy/Developer/tradopad/manage.py runserver 8000
Unhandled exception in thread started by <function wrapper at 0x10a8cc848>
Traceback (most recent call last):
File "/Users/andy/tradopad/lib/python2.7/site-packages/django/utils/autoreload.py", line 226, in wrapper
fn(_args, *_kwargs)
File "/Users/andy/tradopad/lib/python2.7/site-packages/django/core/management/commands/runserver.py", line 113, in > inner_run
autoreload.raise_last_exception()
File "/Users/andy/tradopad/lib/python2.7/site-packages/django/utils/autoreload.py", line 249, in raise_last_exception
six.reraise(__exception)
File "/Users/andy/tradopad/lib/python2.7/site-packages/django/utils/autoreload.py", line 226, in wrapper
fn(_args, **kwargs)
File "/Users/andy/tradopad/lib/python2.7/site-packages/django/init.py", line 27, in setup
apps.populate(settings.INSTALLED_APPS)
File "/Users/andy/tradopad/lib/python2.7/site-packages/django/apps/registry.py", line 115, in populate
app_config.ready()
File "/Users/andy/tradopad/lib/python2.7/site-packages/dynamic_preferences/apps.py", line 19, in ready
global_preferences_registry.autodiscover(settings.INSTALLED_APPS)
File "/Users/andy/tradopad/lib/python2.7/site-packages/persisting_theory/registries.py", line 107, in autodiscover
module = import(package)
File "/Users/andy/Developer/tradopad/rater/dynamic_preferences_registry.py", line 4, in
from dynamic_preferences import global_preferences_registry
ImportError: cannot import name global_preferences_registry

The correct way to import global_preferences_registry seems to be from dynamic_preferences.registries import global_preferences_registry -- but it doesn't work either (the same error). I've been only able to do from dynamic_preferences.registries import user_preferences_registry

Setup some validation on section and preference code name

Since we are using preferences and section names in URL's and as key for dictionnaries, we must ensure all of them use alphanumeric characters and underscores only (no hyphen, because accessing a key in a django template would not work, e.g {{ global_preferences.discussion.comments-enabled }}.

Section and Preference instanciation should fail and raise a custom exception when called with such invalid names. The error message should contain information about which preference failed, and which characters are allowed in sections and preferences names.

Invalid names

@global_preferences_registry.register
class SiteTitle(StringPreference):
    name = 'my title' # no space allowed

@global_preferences_registry.register
class SiteTitle(StringPreference):
    name = 'my-title' # no hyphen allowed

Section('test section') # same here
Section('test-section') # and here

Valid names

@global_preferences_registry.register
class SiteTitle(StringPreference):
    name = 'my_title' 

@global_preferences_registry.register
class SiteTitle(StringPreference):
    name = 'title1' 

Section('test_section')
Section('test_section1') 

Add a ModelChoice preference

This one will be a bit tricky because we need to delete the preference when the selected model is deleted. It can probably be done with a pre_save or a post_save signal, but we need to think about it.

Help creating preferences on the fly

I am trying to create a queryset filter on the fly per model and per user.

All my ListViews inherit from a class that has a filters(self) property. In that section I want to create a preference for queryset filters using django-filter module.

I have found several issues with my approach:

def filters(self):
            self.filter_name = self.model.__name__ + "FilterSet"
            self.filter_preference_name = "%s_%s_filter" % (self.model._meta.app_label, self.filter_name.lower())
            self.filter_preference_full_name = "Personal__%s_%s_filter" % (self.model._meta.app_label, self.filter_name.lower())
            self.filter_preference_class_name = "FilterPreference%s%s" % (self.model._meta.app_label.title(), self.filter_name)
            if self.filter_preference_full_name not in preferences_manager.keys():
                FilterPref = type(str(self.filter_preference_class_name), (StringPreference,), {
                'section':personal,
                'name':self.filter_preference_name,
                'default': '{}',
                'instance': self.request.user,
                 })
                user_preferences_registry.register(FilterPref)

Some times preference is created but not persistent. When I change from /myob/list to /anotherobj/list myobj list disappears.
Some times preference is not created at first time, and is created after a second reload.

I also have tried creating preference using orm like in tests and does not persist: UserPreferenceModel.objects.get_or_create( instance=self.henri, section="misc", name='is_zombie')[0].value)

I would not like to create a dynamic_preferences_registry files and objects for more than 30 models, plus I am planning to set settings also for tables: per_page and list_mode (table, cards, ...)

Any ideas why my approach is wrong? is it possible?

Thanks in advance.

checkpreferences no longer deletes obsolete preferences

I apologize for not picking up on this while reviewing #68, but I think checkpreferences might not actually delete obsolete preferences now that the fallback preference is in place. I think (but am not 100% sure) that the root cause is because the line pref = p.preference in checkpreferences.py:18 no longer raises a KeyError, since it's now designed to return a fallback preference if it didn't exist, so it returns the fallback preference without exception.

I'd submit a pull request but the solution isn't obvious to me, at least not what would be consistent with the structure you have in mind.

create_default_per_instance_preferences() takes exactly 3 arguments (2 given)

I would like to suggest making the arguments optional in the signal create_default_per_instance_preferences.

The error above was caused by neomodel library and is probably something that should be fixed that end, however, given that this app only cares about created, this fix would make it more robust.

def create_default_per_instance_preferences(sender, created=None, instance=None, **kwargs):

What do you think?

Installation from pip fails in 0.7.1 if done in same command as Django

As of 0.7.1, python setup.py egg_info fails when it tries to import django.conf.settings in a pip installation. I'm running in a virtual environment on Ubuntu 14.04 with Python 3.5, and my pip version is 7.1.2. This also happens in our Travis CI build (which is where I first noticed it), which uses pip 6.0.7, and both Python 3.4 and 3.5.

If I roll back to 0.7 everything works fine.

I tried installing it using pip commands directly, here's what I noticed:

  • If you run pip install Django and then pip install django-dynamic-preferences==0.7.1 it works fine.
  • If you run pip install Django django-dynamic-preferences==0.7.1 it fails with the same error.

That is, it works if you have Django installed first, but if you try to install them both in the same command (or in the same requirements file) then it fails.

It's presumably caused by Django not being around when pip is trying to find the egg_info? I don't really know how pip installs are supposed to work together.

Output:

$ pip install -r requirements.txt 
Collecting Django==1.9.2 (from -r requirements_common.txt (line 1))
  Using cached Django-1.9.2-py2.py3-none-any.whl
Collecting dj-cmd==0.5 (from -r requirements_common.txt (line 2))
Collecting django-appconf==1.0.1 (from -r requirements_common.txt (line 3))
  Using cached django_appconf-1.0.1-py2.py3-none-any.whl
Collecting django-dynamic-preferences==0.7.1 (from -r requirements_common.txt (line 4))
  Using cached django-dynamic-preferences-0.7.1.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 20, in <module>
      File "/tmp/pip-build-rstucvqq/django-dynamic-preferences/setup.py", line 7, in <module>
        import dynamic_preferences
      File "/tmp/pip-build-rstucvqq/django-dynamic-preferences/dynamic_preferences/__init__.py", line 1, in <module>
        from .dynamic_preferences_registry import user_preferences_registry, global_preferences_registry
      File "/tmp/pip-build-rstucvqq/django-dynamic-preferences/dynamic_preferences/dynamic_preferences_registry.py", line 1, in <module>
        from .registries import PreferenceRegistry, PerInstancePreferenceRegistry
      File "/tmp/pip-build-rstucvqq/django-dynamic-preferences/dynamic_preferences/registries.py", line 32, in <module>
        from .managers import PreferencesManager
      File "/tmp/pip-build-rstucvqq/django-dynamic-preferences/dynamic_preferences/managers.py", line 3, in <module>
        from .settings import preferences_settings
      File "/tmp/pip-build-rstucvqq/django-dynamic-preferences/dynamic_preferences/settings.py", line 4, in <module>
        from django.conf import settings
    ImportError: No module named 'django'

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-rstucvqq/django-dynamic-preferences

Requirements file looks like this:

Django==1.9.2
dj-cmd==0.5
django-appconf==1.0.1
django-dynamic-preferences==0.7.1
django-debug-toolbar==1.4.0
django-extensions==1.6.1
django-ipware==1.1.3
django-jet==0.1.4
psycopg2==2.6.1
six==1.10.0
sqlparse==0.1.18
whitenoise==2.0.6
waitress==0.8.10
django-compressor==2.0
django-libsass==0.6

Invalidate cache on all post_save signals?

In models.py invalidate_cache is called on all model saves:

 post_save.connect(invalidate_cache)

Should it be this?

post_save.connect(invalidate_cache, sender=UserPreferenceModel)
post_save.connect(invalidate_cache, sender=PerInstancePreferenceModel)
post_save.connect(invalidate_cache, sender=GlobalPreferenceModel)

I found this when trying to work out how to invalidate template cache on UserPreferenceModel update as I'm caching template fragments that need to be cleared if the user changes their preferences. My plan is to connect to the post_save signal and call an additional invalidate cache routine.

Install via requirements file fails

Installing with a requirements file such as:

django
django-dynamic-preferences

Fails with the following stack trace :

Collecting django (from -r requirements.txt (line 1))
  Using cached Django-1.8.3-py2.py3-none-any.whl
Collecting django-dynamic-preferences (from -r requirements.txt (line 2))
  Downloading django-dynamic-preferences-0.4.3.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 20, in <module>
      File "/tmp/pip-build-9g6wc0w2/django-dynamic-preferences/setup.py", line 7, in <module>
        import dynamic_preferences
      File "/tmp/pip-build-9g6wc0w2/django-dynamic-preferences/dynamic_preferences/__init__.py", line 1, in <module>
        from .dynamic_preferences_registry import user_preferences_registry, global_preferences_registry
      File "/tmp/pip-build-9g6wc0w2/django-dynamic-preferences/dynamic_preferences/dynamic_preferences_registry.py", line 1, in <module>
        from .registries import PreferenceRegistry, PerInstancePreferenceRegistry
      File "/tmp/pip-build-9g6wc0w2/django-dynamic-preferences/dynamic_preferences/registries.py", line 3, in <module>
        from django.conf import settings
    ImportError: No module named 'django'

This is caused by django settings imported in the __init__.py file.

MySQL Compatibility?

I was able to get a clean install working with a sqlite db, but not with MySQL.

  Applying dynamic_preferences.0001_initial...Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/commands/migrate.py", line 161, in handle
    executor.migrate(targets, plan, fake=options.get("fake", False))
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/executor.py", line 68, in migrate
    self.apply_migration(migration, fake=fake)
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/executor.py", line 102, in apply_migration
    migration.apply(project_state, schema_editor)
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/migration.py", line 108, in apply
    operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/operations/models.py", line 278, in database_forwards
    getattr(new_model._meta, self.option_name, set()),
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/schema.py", line 308, in alter_unique_together
    self.execute(self._create_unique_sql(model, columns))
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/schema.py", line 103, in execute
    cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 81, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/mysql/base.py", line 129, in execute
    return self.cursor.execute(query, args)
  File "/usr/lib/python2.7/dist-packages/MySQLdb/cursors.py", line 174, in execute
    self.errorhandler(self, exc, value)
  File "/usr/lib/python2.7/dist-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
django.db.utils.OperationalError: (1170, "BLOB/TEXT column 'section' used in key specification without a key length")

Cannot flush DB when user preference model app is not installed

If I try to run python manage.py flush with the global dynamic preferences app installed, but the per-user preferences app not installed, I get the following error:

CommandError: Database blhdjango couldn't be flushed. Possible reasons:
  * The database isn't running or isn't configured correctly.
  * At least one of the expected database tables doesn't exist.
  * The SQL was invalid.
Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.
The full error: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "dynamic_preferences_users_userpreferencemodel" references "auth_user".
HINT:  Truncate table "dynamic_preferences_users_userpreferencemodel" at the same time, or use TRUNCATE ... CASCADE.

My guess is that the issue arises because the user preference model tables are created and then moved in the global app's migrations, even though the table is never used in the global preferences app. Is there a way to fix this so that I don't need to go through the process of constructing manual SQL code to clear the database every time that I want to load new data into my dev instance?

Syntax and other corrections in documentation

There are some syntax flaws, logical and spelling mistakes I noticed reading the documentation, e.g.

  • Links use Markdown syntax instead of reStructuredText syntax in README.rst (introduction chapter) and AUTHORS.rst (i.e. the Credits section).
  • "PyPi" should be spelled with a captial eye ("PyPI") in installation.rst.
  • No need for python manage.py syncdb in installation.rst if you support Django 1.7+ only.
  • "nex time" should be "next time" (tee missing) in quickstart.rst, "Under The Hood" section.
  • The Pull Request Guidelines in the Contributing chapter mention Python 2.6, 2.7, and 3.3 whereas the README talks about "Python 2.7 and 3.4, with django 1.7 and 1.8".
  • The README is included in the documentation twice, as an introduction and as a final chapter.

Potential improvements:

  • The chapters Upgrade and Changelog could be combined somehow for easier understanding and maintenance.

Hope that helps. It's not a PR, but better than no feedback at all. πŸ˜”

No such preference in GlobalPreferenceRegistry with section=None and name=

Not quite sure what's happening here. This should be a clean install. The setup guide was followed accurately.

Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
  111.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in wrapper
  583.                 return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapped_view
  105.                     response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/cache.py" in _wrapped_view_func
  52.         response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/sites.py" in inner
  206.             return view(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapper
  29.             return bound_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapped_view
  105.                     response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in bound_func
  25.                 return func.__get__(self, type(self))(*args2, **kwargs2)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in changelist_view
  1575.             media = self.media + formset.media
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py" in media
  393.             return self.empty_form.media
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py" in empty_form
  185.             empty_permitted=True,
File "/usr/local/lib/python2.7/dist-packages/dynamic_preferences/admin.py" in __init__
  15.         super(PreferenceChangeListForm, self).__init__(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py" in __init__
  315.             self.instance = opts.model()
File "/usr/local/lib/python2.7/dist-packages/dynamic_preferences/models.py" in __init__
  46.                 self.value = self.preference.default
File "/usr/local/lib/python2.7/dist-packages/django/utils/functional.py" in __get__
  55.         res = instance.__dict__[self.func.__name__] = self.func(instance)
File "/usr/local/lib/python2.7/dist-packages/dynamic_preferences/models.py" in preference
  50.         return self.registry.get(section=self.section, name=self.name)
File "/usr/local/lib/python2.7/dist-packages/dynamic_preferences/registries.py" in get
  130.                 self.__class__.__name__, section, name))

Exception Type: KeyError at /dynamic_preferences/globalpreferencemodel/
Exception Value: 'No such preference in GlobalPreferenceRegistry with section=None and name='

Unable to retrieve in models.py

Following the section below on the quickstart page, I'm unable to retrieve a global preference in my models.py. The error I get is this:
AttributeError: 'NoneType' object has no attribute 'objects'

https://django-dynamic-preferences.readthedocs.io/en/stable/quickstart.html#retrieve-and-update-preferences

I also tried a different method:
models.py

from dynamic_preferences.models import GlobalPreferenceModel

global_preferences = GlobalPreferenceModel.registry.manager()

full_name = global_preferences['Names__full_name']

And I get this error:
dynamic_preferences.exceptions.NotFoundInRegistry: No such preference in GlobalPreferenceRegistry with section=Names and name=full_name

When calling this in my views.py it works just fine

full_name = global_preferences['Names__full_name']

So why can't I get it to work in models.py?

Unable to set optional preferences

It would be nice to be able to allow 'optional' preferences. For example is currently possible to set an empty string as the default for the StringPreference type, but it is not possible to set it to an empty string from admin, - it comes back with This field is required..

Something like:

@global_preferences_registry.register
class Thing(StringPreference):

    section = site
    name = 'string_thing'
    help_text = 'an optional string that can be set to empty string if you want'
    default = ''
    required = False
    # or maybe:
    # blank = True

Get a preference without specifying its section

Currently I'm getting a preference using

instance.preferences.section_name__preference_name

However, i'd like to be able to get it in a fashion more like

instance.preferences.preference_name

So that changes to the naming of a section, or the section value of a preference do not require reworking all of the times when I get a particular preference. Obviously this could raise an issue when two preferences have the same name, but I feel like this wouldn't be a problem for most uses. Perhaps there could be something along the lines of:

instance.preferences.get_by_name.preference_name

Would that be a good idea? Is there already a way of doing this that I'm missing?

New sections_url_name is not backwards-compatible

Hi! This isn't too big a deal but I thought I'd mention in case you weren't aware and wanted to include it in your documentation somewhere: 0.8.3 isn't backwards-compatible with existing projects using 0.8.2, because it requires a new class property sections_url_name (in 723f2ec). I've implemented it on our side (TabbycatDebate/tabbycat#397) so no worries in terms of functionality for us, just wanted to raise a flag πŸ˜ƒ

Thanks again for all your work on this app, it's super helpful for our project!

Change CharField max_length from 255 to 191 for support of utf8mb4 when using db_index=True

Hi

Would it be possible to change max_length of CharFields where db_index=True in the models from 255 to a value usable with encoding utf8mb4?

From:

class BasePreferenceModel(models.Model):
    section = models.CharField(
        max_length=255, db_index=True, blank=True, null=True, default=None)
    name = models.CharField(max_length=255, db_index=True)

To for example:

class BasePreferenceModel(models.Model):
    section = models.CharField(
        max_length=191, db_index=True, blank=True, null=True, default=None)
    name = models.CharField(max_length=191, db_index=True)

Having section and name thats 255 characters long feels like overkill, and changing it to a smaler value will also make it usable in mysql without enabling "innodb_large_prefix" when using true utf8 support. In my case, I dont have access to change the innodb_large_prefix setting.

utf8mb4 max key length at the moment is 191 characters

When i try to run python manage.py syncdb it throws an exception.

pymysql.err.InternalError: (1071, 'Specified key was too long; max key length is 767 bytes')

Best regards
/falknes

Wrong package location using pip install on webfaction hosting

Hi Eliot,

I've been using your module locally, and love it really.

Today, I tried installing the the module on a webfaction server and I came across an issue, not sure whether it's something to do with webfaction's setup or something missing in the module itself.

Firstly pardon my limited knowledge pip.

On webfaction, when I install the modules using pip2.7, all of the modules are installed under /home/myuser/lib/python2.7

However, when i installed django-dynamic-preferences, for some reason the dynamic_preferences module was created under /home/myuser/lib/python2.7/home/myuser/lib/python2.7/dynamic_preferences instead of just /home/myuser/lib/python2.7/dynamic_preferences

so I added the /home/myuser/lib/python2.7/home/myuser/lib/python2.7/dynamic_preferences to my path to resolve the no module found issue. But syncdb wouldn't work. I had to manually copy the dynamic_preferences directory to /home/myuser/lib/python2.7

Would you have any idea why would that have happened with this module only?

Any advise would be appreciated.

Thank you,
Haroon.

"section" should be passed in context of PreferenceFormView

I am modifying the default version of the preference form templates in order to integrate the design with my overall site. However, the PreferenceFormView currently provides very little context information, and it would be very useful in particular to have the current section (for section-specific page titles).

Something like this in views.PreferenceFormView.get_context_data would be a start:

context['section'] = self.kwargs.get('section', None)

However, since it would also be particularly helpful to have the verbose name, I won't put this through as a pull request.

When a preference is removed from the registry, it crashes when loading from database

When you remove a preference from the registry, it currently crashes because it can't find the preference it's trying to restore from the database (because the deleted preference is still in the database).

Here's the traceback when you load a form: http://dpaste.com/2JHWJCN
Here's the traceback when you load the admin page: http://dpaste.com/02CZKTB

I'm not sure what the intended behavior is here, or even what it should be. I was inclined to say it should just fail silently, ignoring the preference and leaving it in the database, but that approach has problems of its own. Perhaps it should log a warning or something, or maybe it should delete the preference from the database. It'd probably be nice for it to do something other than crash completely.

This might seem like something that never changes, but projects might like to deprecate preferences from time to time (this was why it happened to me). The onus is of course on the project developer to do something sensible with deprecated options. Any thoughts?

Convert sections to Python objects

When I designed sections, I thought of the section name as a slug, short identifier, not a verbose name that will be displayed to end users. But we need to store additional data on sections, such as a verbose, translatable name, and maybe some common behaviour that will apply on all preferences.

So, the current section system, based on a single identifier has to evolve to plain Python objects. The API could probably looks like :

@sections.register
class CMSPosts(Section):
    name = 'cms_post'
    verbose_name = _('CMS Posts')

The challenge here will be to keep backward-compatibility.

Per-preference permissions

Right now, all global preferences are editable if you have the correct django permission.

However, we can imagine that, in some workflows, some global preferences access / modification should splitted on a per-group / per-user basis.

E.g, consider a featured_blog_post preference and a third_party_api_key preference. The first one is probably only a concern for the marketing team, while the latter should be restricted to sysadmin's / developpers.

We therefore need a way to create per preference permissions, and integrate these permissions with django's groups / users system. We can also imagine section permissions, that will grant access to the underlying preferences.

AttributeError: 'unicode' object has no attribute 'name'

I'm hitting the following AttributeError exception when trying to access dynamic preferences (global, user, or custom) in Django admin:

File "/path/to/my/virtualenv/lib/python2.7/site-packages/dynamic_preferences/models.py", line 72, in __repr__
return '{0} - {1}/{2}'.format(self.__class__.__name__, self.section.name, self.name)

AttributeError: 'unicode' object has no attribute 'name'

After debugging the error, I noticed that self.section is an Unicode object and hence cannot have a 'name' attribute. If self.section.name is replaced with self.section, everything works.

Environment: Python 2.7.9, Django 1.8.5, django-dynamic-preferences 0.6.2

Enable instance preferences generation via admin

Right now, there is now easy way to generate the default preferences for a model instance (such as User). We should provide a shortcut in admin to allow populating of default preference, which is required for further editing.

add a file preference type

For automated docker deployment I am looking for a way to move all security related info out of the project (passwords, ssl certs, api keys).

I am stuck with ssl certificates.

A file type in dynamic-preferences would allow to include a default self-signed cert in the source code of the project which could be overridden by the real certificate (using conditional configuration in the web server).

ImportError when application configuration class is specified in INSTALLED_APPS

python 3.5.1 windows x64
Django==1.9.4
django-dynamic-preferences==0.8.1
persisting-theory==0.2.1

When in I specify application configuration class myapp.apps.MyAppConfig instead of application package in INSTALLED_APPS there is ImportError:

File "C:\Tools\Python3\lib\site-packages\dynamic_preferences\apps.py", line 19, in ready
global_preferences_registry.autodiscover(settings.INSTALLED_APPS)
File "C:\Tools\Python3\lib\site-packages\persisting_theory\registries.py", line 102, in autodiscover
app_package = import(app)
ImportError: No module named 'myapp.apps.MyAppConfig'; 'myapp.apps' is not a package

When I specify only 'myapp' package there is no ImportError, but I can't set verbose_name etc for my application.

I'm rather new to Python and may be the question is stupid (or related to persisting_theory, not dynamic_preferences) but I stuck on it and need help.

Multi-value "list of choices" preference type

Our use-case is as follows:

  • We would like the user to be able to specify a list of things in a single preference.
  • Each "thing" is one of a set of predefined choices (like in ChoicePreference).
  • On a preference form, the user should be presented with a series of (ordered) dropdown boxes.
  • The allowed choices for all elements in the list are the same.
  • Choices may be selected multiple times in the list, or not at all. So it's not, for example, "please rank these from 1 to 5".
  • It'd be nice if the length of the list were variable, but that'd be hard, so we just ran a fixed-length list, with the ability to leave some blank.

In our specific case, users are specifying how teams in a competition should be ranked. They can choose from a predefined list of metrics ("number of wins", "total score", "number of judges that voted for you"). Teams are then ranked first by the first metric, then by the second metric, and so on.

Here's an implementation:
https://github.com/czlee/tabbycat/blob/43ce83615f8f5bf3dd6d4ba8b7f45ba54f0c53d1/options/forms.py
https://github.com/czlee/tabbycat/blob/43ce83615f8f5bf3dd6d4ba8b7f45ba54f0c53d1/options/serializers.py
https://github.com/czlee/tabbycat/blob/43ce83615f8f5bf3dd6d4ba8b7f45ba54f0c53d1/options/types.py

Here's how it works:

  • The preference type is MultiValueChoicePreference, and takes the following attributes that subclasses specify: choices, nfields and allow_empty.
  • There is a serializer MultiValueSerializer that just converts a list to a delimited string, and back again. The delimiter I've used is "//", but that's easily changed. (The delimiter can't be in choice values, otherwise it'll break.)
  • It uses subclasses of Django's MultiValueField and MultiWidget to implement having several choice fields.
  • If allow_empty is set to true, then the field will purge the list of empty choices as part of its compress() implementation. As a consequence, if a user saves the preference with non-consecutive elements non-empty, the non-empty values will be pushed up so that they are consecutive from the first element. For example, if you save ['A', 'B', None, 'C', None, 'D'], it'll be saved as ['A', 'B', 'C', 'D']. Information about middle empty elements is lost. (This is appropriate for our use-case, at least.)

Screenshot:
image

Hopefully that made senseβ€”it's a little hard to explain fully in text, so sorry if it didn't and feel free to ask more.

Is this use case sufficiently general that you'd be interested in having it added to this project? I'm happy to contribute it to this project, but thought it might be too niche, so thought I'd ask to see what you think.

Allow the order in which preferences are displayed to be specified

I think (having looked at registries.py and the documentation) that there's no current way to specify the order in which preferences are displayed on preference pages. (Please correct me if I'm mistaken.) I think it currently just returns them in the order they come out of the underlying dict, which means that it's a function of the keys and possibly not even consistent (or Python implementation-dependent).

There are a couple of ways I could see this working:

  1. Return them in the order they were added.
  2. Specify a seq or order class attribute in each Preference, which PreferenceRegistry will sort by before returning in preferences().

There'd obviously be a (very minor) performance hit here, so there's possibly an argument for making sorted_preferences a separate method or preferences(sort=True) a keyword argument, so that calls that don't require a sorted list don't need to "wait" for one?

I'll get to this myself eventually, but "eventually" might be a few weeks away so I thought I'd just put this on the radar πŸ˜ƒ

(Sorry, hit Enter at the wrong time before.)

Registry object has no attribute 'create_default_preferences'

When using home-baked preferences and the UserPrefereceModel included in your lib, I cannot trigger default preference generation in neither an interactive shell nor in the django admin.

'OrgPreferenceRegistry' object has no attribute 'create_default_preferences'
and
'UserPreferenceRegistry' object has no attribute 'create_default_preferences'

Where exactly is create_default_preferences defined? When can I expect default preferences to be created?

This is the function being triggered on post_save:

def create_default_per_instance_preferences(sender, created, instance, **kwargs):
    """Create default preferences for PerInstancePreferenceModel"""

    if created:
        try:
            registry = preference_models.get_by_instance(instance)
            registry.create_default_preferences(instance)
        except AttributeError:
            pass

I am using Django 1.11 and python 3.6. I can create stubs of the code to help if required.

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.