2

Custom management command, oauth.py, needs a model from another module. When I include "from appname.authentication.models import Contact" I get "AttributeError: 'module' object has no attribute 'models'." - Im stuck on django 1.6 until I able to build an test suite to help with the upgrade. How do I correctly import Contact?

Other notable SO answers:

Each directory other than /app has an __init__.py . /app is in sys.path/ django directory, /app:

util
-management
--commands
---oauth.py
appname
-authentication
--models.py
extouth.py

extoauth.py is standalone script with the same import and works, but only in manage.py shell. The custom management command will be better.

oauth.py:

import sys
from optparse import make_option
from provider.oauth2.models import Client
from appname.authentication.models import Contact
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Creates OAUTH user and gets access token.'

    option_list = BaseCommand.option_list + (
    make_option('--create-client',
            dest='create_client',
                help='''Returns tuple of <id,secret>...'''),
            make_option('--get-access-token',
                dest='get_access_token',
                help='''Returns time limited access token...'''),
    )

    def handle(self, *args, **options):
        if options['create_client']:
              return  self.create_client(options['create_client'])
        elif options['get_access_token']:
            self.get_access_token()

    def create_client(self, user):
        return user

    def get_access_token(self):
        pass

Console out:

Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 399, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 392, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 272, in fetch_command
    klass = load_command_class(app_name, subcommand)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 75, in load_command_class
    module = import_module('%s.management.commands.%s' % (app_name, name))
  File "/usr/local/lib/python2.7/site-packages/django/utils/importlib.py", line 40, in import_module
    __import__(name)
  File "/app/waapiutil/management/commands/oauth.py", line 4, in <module>
    from wowza.authentication.models import Contact
  File "/app/wowza/authentication/models.py", line 80, in <module>
    class 
SalesforceModel(with_metaclass(salesforce.models.SalesforceModelBase, models.Model)):
AttributeError: 'module' object has no attribute 'models'

hypo - settings is not getting imported So my settings must be getting set just as they do with the manage.py shell usage because if I include at the top of my file: from django.conf import settings settings.configure()

I get:

Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 399, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 392, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 272, in fetch_command
    klass = load_command_class(app_name, subcommand)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 75, in load_command_class
    module = import_module('%s.management.commands.%s' % (app_name, name))
  File "/usr/local/lib/python2.7/site-packages/django/utils/importlib.py", line 40, in import_module
    __import__(name)
  File "/app/waapiutil/management/commands/oauth.py", line 2, in <module>
    settings.configure()
  File "/usr/local/lib/python2.7/site-packages/django/conf/__init__.py", line 89, in configure
    raise RuntimeError('Settings already configured.')
RuntimeError: Settings already configured.

hypo - deeper syntax error (that should have broken production anyway) searching for occurrences of models.model in my app files yields four results, each has the correct capitalization of models.Model.

hypo - Contact is already imported When I comment out the import and run the command i get:

Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 399, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 392, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/base.py", line 242, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File "/app/waapiutil/management/commands/oauth.py", line 23, in handle
    return  self.create_client(options['create_client'])
  File "/app/waapiutil/management/commands/oauth.py", line 32, in create_client
    c = Client(user=Contact.objects.get_by_email(e), name=n,
NameError: global name 'Contact' is not defined

Snippets from authentication/models.py for hynekcer's comment

# Core Django imports
from django.db import models
from django.core.validators import MinLengthValidator
from django.utils.six import with_metaclass

# Third-party imports
import pycountry
from rest_framework.compat import oauth2_provider
import salesforce
from salesforce import fields
from salesforce.backend import manager

...

class SalesforceManager(manager.SalesforceManager):
    """
    Override the default Salesforce manager so we can get some proper REST framework exceptions
    """

    def get(self, *args, **kwargs):
        try:
            result = self.get_queryset().get(*args, **kwargs)
        except self.model.MultipleObjectsReturned:
            raise MultipleUniqueRecords()
        except Exception as e:
            logger.warning("SalesForce exception %s", str(e))
            raise NoRecord()

        return result
class SalesforceModel(with_metaclass(salesforce.models.SalesforceModelBase, models.Model)):
    """
    Abstract model class for Salesforce objects.
    """
    _base_manager = objects = SalesforceManager()
    _salesforce_object = True

    class Meta:
        managed = False
        abstract = True

    Id = fields.SalesforceAutoField(primary_key=True)

    def clean_fields(self, *args, **kwargs):
        # Override the default clean_fields method so we can catch validation exceptions
        try:
            super(SalesforceModel, self).clean_fields(*args, **kwargs)
        except Exception as validation_exception:
            detail = ''
            for field, message in validation_exception.error_dict.items():
                detail += field + ': ' + message[0].messages[0]
            raise ValidationError(detail)

    def save(self, *args, **kwargs):
        # Override the default save method so we can remove fields that Salesforce manages
        if self._meta.model_name in ['contact', 'account', 'license', 'sslcertificate']:
            if not self.Id:
                for field in self._meta.fields:
                    if field.attname == 'created_date' or field.attname == 'certificate_id':
                        self._meta.fields.remove(field)
            else:
                update_fields = self._meta.get_all_field_names()

                remove_list = []
                if self._meta.model_name == 'contact':
                    remove_list = ['created_date', 'accesstoken', 'refreshtoken', 'oauth2_client', 'grant', 'Id', 'entitlement_plan']
                elif self._meta.model_name == 'account':
                    remove_list = ['created_date', 'account', 'Id']
                elif self._meta.model_name == 'license':
                    remove_list = ['created_date', 'Id']
                elif self._meta.model_name == 'sslcertificate':
                    remove_list = ['certificate_id', 'created_date', 'Id']

                for remove_field in remove_list:
                    if remove_field in update_fields:
                        update_fields.remove(remove_field)
                kwargs['update_fields'] = update_fields

        # Retry five times if there's a SalesforceError
        delay = 1
        for retry in range(5):
            try:
                super(SalesforceModel, self).save(*args, **kwargs)
                break
            except Exception as e:
                logger.error("Saving {0} resulted in an error {1}, retry {2}".format(str(self),str(e),retry))
                if retry < 4 and "SERVER_UNAVAILABLE" in str(e):
                    time.sleep(delay)
                    delay *= 2 
                else:
hynekcer
  • 14,942
  • 6
  • 61
  • 99
Tom
  • 981
  • 11
  • 24
  • Looking at the first traceback, it looks like it actually has to do with your salesforce import: `SalesforceModel(with_metaclass(salesforce.models.SalesforceModelBase, models.Model)):` I think it's complaining about `salesforce.models`. – Joey Wilhelm Jul 20 '15 at 17:45
  • The app uses the django-salesforce package from https://pypi.python.org/pypi/django-salesforce - but if the problem was with the package and not with my code, then my manage.py shell < extoauth.py script should throw the same error. The error only happens as a management command. Is the env for management command different than for shell? – Tom Jul 20 '15 at 19:23
  • What if you put that `import Contact` line in under handle (or where you need it)? – François Constant Jul 20 '15 at 23:40
  • @François that works! buy why? I have a name conflict that the smaller scope of putting the import statement with the create_client() method avoids? What about hypo - Contact is already imported? – Tom Jul 21 '15 at 00:23
  • The last line of the traceback is strange (probably incomplete). Please copy important parts of the module `/app/wowza/authentication/models.py`: How looks the import of names `models` and other names relevant for django.db and salesforce, what is on the line 32 +- two lines, how looks the base of your class Contact? – hynekcer Jul 21 '15 at 02:34
  • @Tom I'm not sure why but that's usually the easiest way to avoid circular import... – François Constant Jul 21 '15 at 02:56
  • @hynekcer thanks for the detailed questions. I do not understand the app very well yet and am new to DRF too. Tracebacks are complete. Updated with the Salesforce Models and the import statements. – Tom Jul 22 '15 at 03:38
  • If you can write a very simplified demo of the problem to github temporarily, I can answer. Commit 1: after django-admin startproject FOO; startapp BAR or copied example project from salesforce/testrunner. Commit 2 after <= 200 added lines, still working. Commit 3: broken after <= 20 added important lines. Smaller is better. Don't commit local_settings because they are private. – hynekcer Jul 22 '15 at 19:21

1 Answers1

0

(This is a comment requiring much place, not the answer yet.)

I thought my eyes deceiving when I saw a line from my database backend in your application code, maybe also with a concrete model in the same module. I see that your pasted code is based on an older django-salesforce 0.5.x code. OK, hopefully you use also the package 0.5.x. It probably can not work with the current django-salesforce 0.6 or nobody should expect it

The most safe style is to use the public API. A more fragile style is to use also undocumented features. The most fragile is to copy-paste code from the core of other package and to forget that you started to use a new version. You must have a sufficiently serious reason and enough time if you do such things. (I did it six times for every Django version because the django-salesforce backend is interesting enough for me, but I don't reccomend it to you.)

You can write simply:

class MySalesforceModel(salesforce.models.SalesforceModel):
    def ...  # your custom method

    class Meta:
        managed = False
        abstract = True

The advantage is that this will use the right current SalesforceModel even with the next version of Django and django-salesforce.

If you really need to customize a backend, please comply a sane separation of the backend and application code to different modules so that the backend can be imported without importing any concrete model. Otherwise you can easy get ugly dependencies. Also a nonempty 'init.py` can cause sily dependencies

You need no hypothesis ("hypo") for such simple things if you have the complete source code. You can simply log (print) that information e.g. put import sys; print("settings imported", "my_project.settings" in sys.modules) or verify that settins are correctly configured - put temporarily a line just before a problematic place: from django.conf import settings; print(settings.INSTALLED_APPS) # any valid name

You should check syntax errors before asking a help. It can be done easily for any specific Python version for a tree of subdirectories e.g. by python2.7 -m compileall $(pwd)

It is very insane to use a general handling except Exception: especially while you are debugging. Imagine that the code between try...except will raise an ImportError and you swallow it.

It can be easier to solve dependencies by refactoring the code than to maintain the fragile code later. Imagine that you import inside a function and you get a strange ImportError message from your friend only sometimes if the module is imported from a thread only in Apache, but it succeeds if you import it from the main thread. Explicit import is favoured.

hynekcer
  • 14,942
  • 6
  • 61
  • 99
  • hyneker, thank your for your thoughtful response and suggestions. this app is not my creation, but I have been assigned the task of taking care of it and debugging it. – Tom Aug 07 '15 at 19:26