django-dynamic-preferences icon indicating copy to clipboard operation
django-dynamic-preferences copied to clipboard

'NoneType' object has no attribute '__name__'

Open gonzaloamadio opened this issue 5 years ago • 12 comments

I have installed the library, define a preference , but when try to use it in my code, I got this.

  File "/home/gonzalo/Playground/tektank-ws/tektank/tektank/apps/entities/models.py", line 27, in _validate_dob
    oag = global_preferences['general__oldest_allowed_age']
  File "/home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py", line 29, in __getitem__
    return self.get(key)
  File "/home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py", line 133, in get
    return self.from_cache(section, name)
  File "/home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py", line 63, in from_cache
    self.get_cache_key(section, name), CachedValueNotFound)
  File "/home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py", line 57, in get_cache_key
    return 'dynamic_preferences_{0}_{1}_{2}'.format(self.model.__name__, section, name)
AttributeError: 'NoneType' object has no attribute '__name__'

This is my code:

from dynamic_preferences.registries import global_preferences_registry  
global_preferences = global_preferences_registry.manager()              
                                                                        
def _validate_dob(value):                                                                                                              
    oag = global_preferences['general__oldest_allowed_age']             
    yag = global_preferences['general__youngest_allowed_age']           
    if not oag <= value <= yag:                                         
        raise ValidationError(                                          
            _("Date must be between %(oag)s and %(yag)s"),              
            code='invalid_model_data',                                  
            params={'oag':oag.strftime("%x"), 'yag':yag.strftime("%x")},
        )                                                               
                                                                        

I found this issue, but it does not give the solution with code. https://github.com/EliotBerriot/django-dynamic-preferences/issues/163

gonzaloamadio avatar Mar 30 '19 05:03 gonzaloamadio

UPDATE:

If I force the parameter no_cache=True, i.e, I change for this lines in the function:

def _validate_dob(value):                                                                                                              
    oag = global_preferences.get('general__oldest_allowed_age', no_cache=True)  
    yag = global_preferences.get('general__youngest_allowed_age', no_cache=True)

I got this error instead

  File "/home/gonzalo/Playground/tektank-ws/tektank/tektank/apps/entities/models.py", line 29, in _validate_dob
    oag = global_preferences.get('general__oldest_allowed_age', no_cache=True)
  File "/home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py", line 130, in get
    return self.get_db_pref(section=section, name=name).value
  File "/home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py", line 144, in get_db_pref
    except self.model.DoesNotExist:
AttributeError: 'NoneType' object has no attribute 'DoesNotExist'

I have reviewd the code, and in registries.py, there is this code, and preference_model is None..

class PreferenceRegistry(persisting_theory.Registry):
...
    preference_model = None                          

My idea is that I have to execute something to register preferences, but I do not find it in the documentation.

gonzaloamadio avatar Apr 03 '19 08:04 gonzaloamadio

One more thing. If I execute the same code, in my django shell. It works ok.

In [1]: from dynamic_preferences.registries import global_preferences_registry In [2]: global_preferences = global_preferences_registry.manager()
In [3]: global_preferences.get('general__oldest_allowed_age', no_cache=True) Out[2]: 100

So I guess, there is a problem with the registration of the app, or something like that. But cant figure it out

gonzaloamadio avatar Apr 06 '19 04:04 gonzaloamadio

@gonzaloamadio how are you running this script? Do you call django.conf.settings.configure at some point? https://docs.djangoproject.com/en/2.1/topics/settings/#either-configure-or-django-settings-module-is-required

agateblue avatar Apr 08 '19 08:04 agateblue

@EliotBerriot

It is a validator of a model. So I have the error while trying to save one instance of a model in the admin.

So the server is running, and settings should be already configured as I run the server with the command "python manage.py runserver 0.0.0.0:8888"

class MyModel(models.Model):
  birth_date = models.DateField(       
    verbose_name=_("date of birth"), 
    null=True,                       
    blank=True,                      
    validators=[_validate_dob],
  )                                    

gonzaloamadio avatar Apr 09 '19 01:04 gonzaloamadio

Just to expand a comment I made before. This is a snippet of the PreferenceRegistry, in registries.py

class PreferenceRegistry(persisting_theory.Registry):
    // SOME OTHER CODE
    preference_model = None 
    def manager(self, **kwargs):                                                 
        return PreferencesManager(registry=self, model=self.preference_model, **kwargs)
class PreferencesManager(collections.Mapping):      
    def __init__(self, model, registry, **kwargs):  
        self.model = model                          <-- This is None
        self.registry = registry                    
        self.instance = kwargs.get('instance')      
                                                    
    @property                                       
    def queryset(self):                             
        qs = self.model.objects.all()               
        if self.instance:                           
            qs = qs.filter(instance=self.instance)  
        return qs                                   

I have put a pdb into the manager queryset function and init function. This is in my django shell_plus

In [1]: settings.configured
Out[1]: True
In [2]: from dynamic_preferences.registries import global_preferences_registry
   ...: global_preferences = global_preferences_registry.manager()
> /home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py(13)__init__()
-> self.model = model
(Pdb) model                           **<--- THIS LOOKS OK**
<class 'dynamic_preferences.models.GlobalPreferenceModel'>

In [3]: global_preferences.get('general__oldest_allowed_age', no_cache=True)

(Pdb) print(vars(self))
{'model': <class 'dynamic_preferences.models.GlobalPreferenceModel'>, 'registry': GlobalPreferenceRegistry([('general', OrderedDict([('title', <global_parameters.dynamic_preferences_registry.SiteTitle object at 0x7f13ab4d8780>), ('oldest_allowed_age', <global_parameters.dynamic_preferences_registry.OldestAllowedAge object at 0x7f13ab4d86a0>), ('youngest_allowed_age', <global_parameters.dynamic_preferences_registry.YoungestAllowedAge object at 0x7f13ab4d8828>)]))]), 'instance': None}

Out[3] ; 100                         **<-- Indeed, it works ok**

But when I do the same from my code... self.model is None

In [5]: te = Tester.objects.first()
In [6]: old = timezone.now().date()-relativedelta(years=150)
In [7]: te.birth_date = old
In [8]: te.full_clean()
> /home/gonzalo/.virtualenvs/tektank-ws/lib/python3.6/site-packages/dynamic_preferences/managers.py(20)queryset()
-> qs = self.model.objects.all()
(Pdb) print(vars(self))
{'model': None, 'registry': GlobalPreferenceRegistry([('general', OrderedDict([('title', <global_parameters.dynamic_preferences_registry.SiteTitl
e object at 0x7f13ab4d8780>), ('oldest_allowed_age', <global_parameters.dynamic_preferences_registry.OldestAllowedAge object at 0x7f13ab4d86a0>),
 ('youngest_allowed_age', <global_parameters.dynamic_preferences_registry.YoungestAllowedAge object at 0x7f13ab4d8828>)]))]), 'instance': None}

Moroever, self.model and self.instance are None

gonzaloamadio avatar Apr 09 '19 03:04 gonzaloamadio

SOLVED: But still don't know really why, I guess it has something to be with the initialisation process I would like to figure it out

I put the get of the manager inside the function. So instead of having this

from dynamic_preferences.registries import global_preferences_registry  
global_preferences = global_preferences_registry.                                                
def _validate_dob(value):                                                                                                              
    oag = global_preferences['general__oldest_allowed_age']
    // more code

I have now this

from dynamic_preferences.registries import global_preferences_registry                                                  
def _validate_dob(value):                                                                                                              
    global_preferences = global_preferences_registry.manager()              
    oag = global_preferences['general__oldest_allowed_age']
   // more code

gonzaloamadio avatar Apr 09 '19 03:04 gonzaloamadio

Same problem here, putting global_preferences = global_preferences_registry.manager() inside a function fixes the problem but should be investigated more.

lorenzomorandini avatar Jun 26 '19 07:06 lorenzomorandini

I run into the same issue.

I think the problem here is related to the order of entries in INSTALLED_APPS.

DynamicPreferencesConfig registers the GlobalPreferenceModel on ready. It needs to be called before you call global_preferences_registry.manager(). If you call global_preferences_registry.manager() earlier it will create an instance of manager, but won't register GlobalPreferenceModel for it. Cause you put it in the global scope it will call global_preferences_registry.manager() on import i.e. before DynamicPreferencesConfig::ready. Then inside the function you use the object from memory which was created on import. When you place the code inside the function it will create a new object when the function is called.

Potential fixes:

  1. Just update the documentation to highlight this behavior. The current doc recommends to add 'dynamic_preferences' after 'django.contrib.auth'. As far as I understand it's required for UserPreferences (which I'm not using). So in a big project, you will need to play a bit with the order of apps I think to find a working configuration if you want to use UserPreferences.

  2. Fix line 186 in registries.py:

        return PreferencesManager(registry=self, model=self.preference_model, **kwargs)

to something like this:

        return PreferencesManager(registry=self, model=self.preference_model or GlobalPreferenceModel, **kwargs)

@EliotBerriot Which one do you think will be better? I didn't look deep at the other parts of the code. So need to rely on your advice. I can work on either fix.

tyomo4ka avatar Aug 31 '19 03:08 tyomo4ka

@tyomo4ka I tend to prefer a documentation, because instanciating a manager before the apps are all loaded is likely to create many issues down the road.

Basically, using preferences and managers must happen only after django.setup() has returned successfully :)

agateblue avatar Sep 03 '19 13:09 agateblue

@EliotBerriot That makes sense. Thanks for the hint. I will fix it in my project then.

I will also check what is the best place in the documentation to make this note and open a PR.

tyomo4ka avatar Sep 04 '19 00:09 tyomo4ka

Hi @artem, can you share how are you doing? The fix

On Wed, 4 Sep 2019 at 12:59 PM, Artem [email protected] wrote:

@EliotBerriot https://github.com/EliotBerriot That makes sense. Thanks for the hint. I will fix it in my project then.

I will also check what is the best place in the documentation to make this note and open a PR.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/EliotBerriot/django-dynamic-preferences/issues/173?email_source=notifications&email_token=AAHVLY7CN7VBXVFHVO7RAZDQH4B5JA5CNFSM4HCOHZF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5Z77ZI#issuecomment-527695845, or mute the thread https://github.com/notifications/unsubscribe-auth/AAHVLY5ONYFDCOEVT54USRTQH4B5JANCNFSM4HCOHZFQ .

--

Gonzalo Amadio

gonzaloamadio avatar Sep 04 '19 10:09 gonzaloamadio

I will also check what is the best place in the documentation to make this note and open a PR.

@tyomo4ka I think we could use a warning here.

Something like:


.. warning:: 

    All your calls to ``global_preferences_registry.manager()`` must occur after  ``django.setup()`` has run and all your apps are loaded. 

Of course you can elaborate and give some working/not working examples if you feel like it :)

agateblue avatar Sep 04 '19 10:09 agateblue