def check_native_jsonfield_set_on_recent_django_versions(app_configs=None, **kwargs): """ Check that DJSTRIPE_USE_NATIVE_JSONFIELD is set on Django > 3.1. This is only a suggestion, as existing installations need a migration path. """ messages = [] # This error check is skipped on Django < 3.1+, because the native JSONField # was not available outside of Postgres engines then. if django.VERSION < (3, 1): return messages # NOTE: Not using app_settings.USE_NATIVE_JSONFIELD. # Only display this warning if the setting is unset. if not hasattr(settings, "DJSTRIPE_USE_NATIVE_JSONFIELD"): # TODO: Give more details on the migration path messages.append( checks.Warning( "DJSTRIPE_USE_NATIVE_JSONFIELD is not set.", hint=( "On Django 3.1+, setting DJSTRIPE_USE_NATIVE_JSONFIELD = True is " "recommended.\nPre-existing dj-stripe installations may require a " "migration, in which case you may want to set it to False." ), id="djstripe.W005", ) ) return messages
def _check_related_name(self, **kwargs): return ([ checks.Warning( "Setting 'related_name' on a RelatedField may be better!", obj=self, ) ] if self.remote_field.related_name is None else []) # noqa
def deprecate(**kwargs): return [checks.Warning('The app "activesync" is deprecated.', hint='It will probably be removed in the next release if nobody works on it.', obj='activesync', id='creme.activesync.E001', ), ]
def _check_block_types(self, **kwargs): if (not self.block_types): return [ checks.Warning( "'block_types' attribute is empty, so offers no blocks.", id='streamfield.model_fields.W001', ) ] try: block_specs = list(self.block_types) except TypeError: return [ checks.Error( "'block_types' structure must cast to a list.", id='streamfield.model_fields.E001', ) ] errors = [] #for bs in self.block_types: # errors.extend(self._check_block_spec(bs)) keys = [kv[0] for kv in self.block_types] if len(block_specs) != len(set(keys)): return [ checks.Error( "'block_types' value contains duplicate field(s).", id='admin.E002', ) ] return errors
def test_username_non_unique(self): """ A non-unique USERNAME_FIELD raises an error only if the default authentication backend is used. Otherwise, a warning is raised. """ errors = checks.run_checks() self.assertEqual(errors, [ checks.Error( "'CustomUserNonUniqueUsername.username' must be " "unique because it is named as the 'USERNAME_FIELD'.", obj=CustomUserNonUniqueUsername, id='auth.E003', ), ]) with self.settings(AUTHENTICATION_BACKENDS=['my.custom.backend']): errors = checks.run_checks() self.assertEqual(errors, [ checks.Warning( "'CustomUserNonUniqueUsername.username' is named as " "the 'USERNAME_FIELD', but it is not unique.", hint='Ensure that your authentication backend(s) can handle non-unique usernames.', obj=CustomUserNonUniqueUsername, id='auth.W004', ), ])
def check_user_model(app_configs=None, **kwargs): if app_configs is None: cls = apps.get_model(settings.AUTH_USER_MODEL) else: app_label, model_name = settings.AUTH_USER_MODEL.split('.') for app_config in app_configs: if app_config.label == app_label: cls = app_config.get_model(model_name) break else: # Checks might be run against a set of app configs that don't # include the specified user model. In this case we simply don't # perform the checks defined below. return [] errors = [] # Check that REQUIRED_FIELDS is a list if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)): errors.append( checks.Error( "'REQUIRED_FIELDS' must be a list or tuple.", obj=cls, id='auth.E001', )) # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS. if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS: errors.append( checks.Error( "The field named as the 'USERNAME_FIELD' " "for a custom user model must not be included in 'REQUIRED_FIELDS'.", obj=cls, id='auth.E002', )) # Check that the username field is unique if not cls._meta.get_field(cls.USERNAME_FIELD).unique: if (settings.AUTHENTICATION_BACKENDS == [ 'django.contrib.auth.backends.ModelBackend' ]): errors.append( checks.Error( "'%s.%s' must be unique because it is named as the 'USERNAME_FIELD'." % (cls._meta.object_name, cls.USERNAME_FIELD), obj=cls, id='auth.E003', )) else: errors.append( checks.Warning( "'%s.%s' is named as the 'USERNAME_FIELD', but it is not unique." % (cls._meta.object_name, cls.USERNAME_FIELD), hint= 'Ensure that your authentication backend(s) can handle non-unique usernames.', obj=cls, id='auth.W004', )) return errors
def check_migrations_applied(app_configs, **kwargs): """ A Django check to see if all migrations have been applied correctly. """ from django.db.migrations.loader import MigrationLoader errors = [] # Load migrations from disk/DB try: loader = MigrationLoader(connection, ignore_no_migrations=True) except (ImproperlyConfigured, ProgrammingError, OperationalError): msg = "Can't connect to database to check migrations" return [checks.Info(msg, id=health.INFO_CANT_CHECK_MIGRATIONS)] if app_configs: app_labels = [app.label for app in app_configs] else: app_labels = loader.migrated_apps for node, migration in loader.graph.nodes.items(): if migration.app_label not in app_labels: continue if node not in loader.applied_migrations: msg = "Unapplied migration {}".format(migration) # NB: This *must* be a Warning, not an Error, because Errors # prevent migrations from being run. errors.append( checks.Warning(msg, id=health.WARNING_UNAPPLIED_MIGRATION)) return errors
def check(cls, **kwargs): errors = super(AbstractRendition, cls).check(**kwargs) # If a filter_spec column exists on this model, and contains null entries, warn that # a data migration needs to be performed to populate it try: null_filter_spec_exists = cls.objects.filter( filter_spec='').exists() except DatabaseError: # The database is not in a state where the above lookup makes sense; # this is entirely expected, because system checks are performed before running # migrations. We're only interested in the specific case where the column exists # in the db and contains nulls. null_filter_spec_exists = False if null_filter_spec_exists: errors.append( checks.Warning( "Custom image model %r needs a data migration to populate filter_src" % cls, hint= "The database representation of image filters has been changed, and a data " "migration needs to be put in place before upgrading to Wagtail 1.8, in order to " "avoid data loss. See http://docs.wagtail.io/en/latest/releases/1.7.html#filter-spec-migration", obj=cls, id='wagtailimages.W001', )) return errors
def test_context_processor_dependencies(self): expected = [ checks.Error( "'django.contrib.auth.context_processors.auth' must be " "enabled in DjangoTemplates (TEMPLATES) if using the default " "auth backend in order to use the admin application.", id="admin.E402", ), checks.Error( "'django.contrib.messages.context_processors.messages' must " "be enabled in DjangoTemplates (TEMPLATES) in order to use " "the admin application.", id="admin.E404", ), checks.Warning( "'django.template.context_processors.request' must be enabled " "in DjangoTemplates (TEMPLATES) in order to use the admin " "navigation sidebar.", id="admin.W411", ), ] self.assertEqual(admin.checks.check_dependencies(), expected) # The first error doesn't happen if # 'django.contrib.auth.backends.ModelBackend' isn't in # AUTHENTICATION_BACKENDS. with self.settings(AUTHENTICATION_BACKENDS=[]): self.assertEqual(admin.checks.check_dependencies(), expected[1:])
def check_canonical_url(app_configs=None, **kwargs): from django.conf import settings from django.contrib.sites.models import Site errors = [] no_canonical_error = checks.Critical( _("No canonical URL provided and default site set to example.com."), hint=_("Set the `POOTLE_CANONICAL_URL` in settings or update the " "default site if you are using django.contrib.sites."), id="pootle.C018") localhost_canonical_warning = checks.Warning( _("Canonical URL is set to http://localhost."), hint=_( "Set the `POOTLE_CANONICAL_URL` to an appropriate value for your " "site or leave it empty if you are using `django.contrib.sites`."), id="pootle.W020") try: contrib_site = Site.objects.get_current() except (ProgrammingError, OperationalError): if "django.contrib.sites" in settings.INSTALLED_APPS: return [] contrib_site = None uses_sites = (not settings.POOTLE_CANONICAL_URL and contrib_site) if uses_sites: site = Site.objects.get_current() if site.domain == "example.com": errors.append(no_canonical_error) elif not settings.POOTLE_CANONICAL_URL: errors.append(no_canonical_error) elif settings.POOTLE_CANONICAL_URL == "http://localhost": errors.append(localhost_canonical_warning) return errors
def check_library_versions(app_configs=None, **kwargs): from django import VERSION as DJANGO_VERSION from lxml.etree import LXML_VERSION from translate.__version__ import ver as ttk_version errors = [] if DJANGO_VERSION < DJANGO_MINIMUM_REQUIRED_VERSION: errors.append( checks.Critical( _("Your version of Django is too old."), hint=_("Try pip install --upgrade 'Django==%s'", _version_to_string(DJANGO_MINIMUM_REQUIRED_VERSION)), id="pootle.C002", )) if LXML_VERSION < LXML_MINIMUM_REQUIRED_VERSION: errors.append( checks.Warning( _("Your version of lxml is too old."), hint=_("Try pip install --upgrade lxml"), id="pootle.W003", )) if ttk_version < TTK_MINIMUM_REQUIRED_VERSION: errors.append( checks.Critical( _("Your version of Translate Toolkit is too old."), hint=_("Try pip install --upgrade translate-toolkit"), id="pootle.C003", )) return errors
def test_username_non_unique(self): """ A non-unique USERNAME_FIELD should raise an error only if we use the default authentication backend. Otherwise, an warning should be raised. """ errors = checks.run_checks() expected = [ checks.Error( ("'CustomUserNonUniqueUsername.username' must be " "unique because it is named as the 'USERNAME_FIELD'."), hint=None, obj=CustomUserNonUniqueUsername, id='auth.E003', ), ] self.assertEqual(errors, expected) with self.settings(AUTHENTICATION_BACKENDS=['my.custom.backend']): errors = checks.run_checks() expected = [ checks.Warning( ("'CustomUserNonUniqueUsername.username' is named as " "the 'USERNAME_FIELD', but it is not unique."), hint=( 'Ensure that your authentication backend(s) can handle ' 'non-unique usernames.'), obj=CustomUserNonUniqueUsername, id='auth.W004', ) ] self.assertEqual(errors, expected)
def check_stripe_api_version(app_configs=None, **kwargs): """Check the user has configured API version correctly.""" from .settings import djstripe_settings messages = [] default_version = djstripe_settings.DEFAULT_STRIPE_API_VERSION version = djstripe_settings.STRIPE_API_VERSION if not validate_stripe_api_version(version): msg = "Invalid Stripe API version: {}".format(version) hint = "STRIPE_API_VERSION should be formatted as: YYYY-MM-DD" messages.append(checks.Critical(msg, hint=hint, id="djstripe.C004")) if version != default_version: msg = ( "The Stripe API version has a non-default value of '{}'. " "Non-default versions are not explicitly supported, and may " "cause compatibility issues.".format(version) ) hint = "Use the dj-stripe default for Stripe API version: {}".format( default_version ) messages.append(checks.Warning(msg, hint=hint, id="djstripe.W001")) return messages
def check_field_type(self, field, field_type): """ MySQL has the following field length restriction: No character (varchar) fields can have a length exceeding 255 characters if they have a unique index on them. MySQL doesn't support a database index on some data types. """ errors = [] if (field_type.startswith('varchar') and field.unique and (field.max_length is None or int(field.max_length) > 255)): errors.append( checks.Error( 'MySQL does not allow unique CharFields to have a max_length > 255.', obj=field, id='mysql.E001', )) if field.db_index and field_type.lower( ) in self.connection._limited_data_types: errors.append( checks.Warning( '%s does not support a database index on %s columns.' % (self.connection.display_name, field_type), hint=("An index won't be created. Silence this warning if " "you don't care about it."), obj=field, id='fields.W162', )) return errors
def test_middleware_classes_not_set_explicitly(self): # If MIDDLEWARE_CLASSES was set explicitly, temporarily pretend it wasn't middleware_classes_overridden = False if 'MIDDLEWARE_CLASSES' in settings._wrapped._explicit_settings: middleware_classes_overridden = True settings._wrapped._explicit_settings.remove('MIDDLEWARE_CLASSES') try: errors = check_1_7_compatibility() expected = [ checks.Warning( "MIDDLEWARE_CLASSES is not set.", hint= ("Django 1.7 changed the global defaults for the MIDDLEWARE_CLASSES. " "django.contrib.sessions.middleware.SessionMiddleware, " "django.contrib.auth.middleware.AuthenticationMiddleware, and " "django.contrib.messages.middleware.MessageMiddleware were removed from the defaults. " "If your project needs these middleware then you should configure this setting." ), obj=None, id='1_7.W001', ) ] self.assertEqual(errors, expected) finally: # Restore settings value if middleware_classes_overridden: settings._wrapped._explicit_settings.add('MIDDLEWARE_CLASSES')
def _check_supported_storage_provider(self) -> List[checks.CheckMessage]: if not MultipartManager.supported_storage(self.storage): msg = f'Incompatible storage type used with an {self.__class__.__name__}.' logger.warning(msg) return [checks.Warning(msg, obj=self, id='s3_file_field.W001')] else: return []
def directories(**kwargs): for key in [ "prometheus:rules", "prometheus:blackbox", "prometheus:targets" ]: try: path = pathlib.Path(util.setting(key)).parent except TypeError: yield checks.Warning( "Missing setting for %s in %s " % (key, settings.PROMGEN_CONFIG_FILE), id="promgen.W001", ) else: if not os.access(path, os.W_OK): yield checks.Warning("Unable to write to %s" % path, id="promgen.W002")
def check(app_configs, **kwargs): errors = [] # Check for old hvad settings in global namespace for key in dir(djsettings): if key.startswith('HVAD_'): errors.append( checks.Critical( 'HVAD setting in global namespace', hint='HVAD settings are now namespaced in the HVAD dict.', obj=key, id='hvad.settings.C01', )) hvad_settings = getattr(djsettings, 'HVAD', {}) for key, value in hvad_settings.items(): try: checker = getattr(HvadSettingsChecks, 'check_%s' % key) except AttributeError: errors.append( checks.Warning('Unknown setting HVAD[%r]' % key, obj=key, id='hvad.settings.W01')) else: errors.extend(checker(value)) return errors
def test_test_runner_not_set_explicitly(self): # We remove some settings to make this look like a project generated under Django 1.5. old_test_runner = settings._wrapped.TEST_RUNNER del settings._wrapped.TEST_RUNNER settings._wrapped._explicit_settings.add('MANAGERS') settings._wrapped._explicit_settings.add('ADMINS') try: errors = check_1_6_compatibility() expected = [ checks.Warning( "Some project unittests may not execute as expected.", hint=("Django 1.6 introduced a new default test runner. It looks like " "this project was generated using Django 1.5 or earlier. You should " "ensure your tests are all running & behaving as expected. See " "https://docs.djangoproject.com/en/dev/releases/1.6/#discovery-of-tests-in-any-test-module " "for more information."), obj=None, id='1_6.W001', ) ] self.assertEqual(errors, expected) finally: # Restore settings value settings._wrapped.TEST_RUNNER = old_test_runner settings._wrapped._explicit_settings.remove('MANAGERS') settings._wrapped._explicit_settings.remove('ADMINS')
def sites(app_configs, **kwargs): if models.Site.objects.count() == 0: yield checks.Warning( "Site not configured", hint="Missing django site configuration", id="promgen.W006", ) for site in models.Site.objects.filter(pk=settings.SITE_ID, domain__in=["example.com"]): yield checks.Warning( "Promgen is configured to example domain", obj=site, hint="Please update from admin page /admin/", id="promgen.W007", )
def _check_generic_foreign_key_existence(self): target = self.rel.to if isinstance(target, ModelBase): # Using `vars` is very ugly approach, but there is no better one, # because GenericForeignKeys are not considered as fields and, # therefore, are not included in `target._meta.local_fields`. fields = target._meta.virtual_fields if any( isinstance(field, GenericForeignKey) and field.ct_field == self.content_type_field_name and field.fk_field == self.object_id_field_name for field in fields): return [] else: return [ checks.Warning( ('The field defines a generic relation with the model ' '%s.%s, but the model lacks GenericForeignKey.') % (target._meta.app_label, target._meta.object_name), hint=None, obj=self, id='contenttypes.E004', ) ] else: return []
def test_test_runner_not_set_explicitly(self): # If TEST_RUNNER was set explicitly, temporarily pretend it wasn't test_runner_overridden = False if 'TEST_RUNNER' in settings._wrapped._explicit_settings: test_runner_overridden = True settings._wrapped._explicit_settings.remove('TEST_RUNNER') # We remove some settings to make this look like a project generated under Django 1.5. settings._wrapped._explicit_settings.add('MANAGERS') settings._wrapped._explicit_settings.add('ADMINS') try: errors = check_1_6_compatibility() expected = [ checks.Warning( "Some project unittests may not execute as expected.", hint=("Django 1.6 introduced a new default test runner. It looks like " "this project was generated using Django 1.5 or earlier. You should " "ensure your tests are all running & behaving as expected. See " "https://docs.djangoproject.com/en/dev/releases/1.6/#new-test-runner " "for more information."), obj=None, id='1_6.W001', ) ] self.assertEqual(errors, expected) finally: # Restore settings value if test_runner_overridden: settings._wrapped._explicit_settings.add('TEST_RUNNER') settings._wrapped._explicit_settings.remove('MANAGERS') settings._wrapped._explicit_settings.remove('ADMINS')
def check_user_model(**kwargs): errors = [] cls = apps.get_model(settings.AUTH_USER_MODEL) # Check that REQUIRED_FIELDS is a list if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)): errors.append( checks.Error( 'The REQUIRED_FIELDS must be a list or tuple.', hint=None, obj=cls, id='auth.E001', ) ) # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS. if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS: errors.append( checks.Error( ('The field named as the USERNAME_FIELD ' 'must not be included in REQUIRED_FIELDS ' 'on a custom user model.'), hint=None, obj=cls, id='auth.E002', ) ) # Check that the username field is unique if not cls._meta.get_field(cls.USERNAME_FIELD).unique: if (settings.AUTHENTICATION_BACKENDS == ('django.contrib.auth.backends.ModelBackend',)): errors.append( checks.Error( ('The %s.%s field must be unique because it is ' 'pointed to by USERNAME_FIELD.') % ( cls._meta.object_name, cls.USERNAME_FIELD ), hint=None, obj=cls, id='auth.E003', ) ) else: errors.append( checks.Warning( ('The %s.%s field is pointed to by USERNAME_FIELD, ' 'but it is not unique.') % ( cls._meta.object_name, cls.USERNAME_FIELD ), hint=('Ensure that your authentication backend can handle ' 'non-unique usernames.'), obj=cls, id='auth.W004', ) ) return errors
def test_boolean_settings(self): for key, err in (('AUTOLOAD_TRANSLATIONS', 'W02'), ('USE_DEFAULT_QUERYSET', 'W03')): error = checks.Warning('HVAD["%s"] should be True or False' % key, obj=key, id='hvad.settings.%s' % err) with self.settings(HVAD={key: 'foo'}): self.assertIn(error, settings.check(apps))
def check_ow_object_notification_widget_setting(app_configs, **kwargs): errors = [] if not isinstance(app_settings.IGNORE_ENABLED_ADMIN, list): errors.append( checks.Warning( msg='Improperly Configured', hint=( '"OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN" should be a list, ' f'{type(app_settings.IGNORE_ENABLED_ADMIN)} provided' ), obj='Settings', ) ) return errors # Check individual entries of IGNORE_ENABLED_ADMIN for path in app_settings.IGNORE_ENABLED_ADMIN: if not isinstance(path, str): errors.append( checks.Error( msg='Improperly Configured', hint=( '"OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN" should contain ' f'dotted path string to ModelAdmin, found {type(path)}' ), obj='Settings', ) ) continue # Check whether dotted path points to subclass of ModelAdmin class try: model_admin_cls = import_string(path) assert issubclass(model_admin_cls, ModelAdmin) except ImportError: errors.append( checks.Error( msg='Improperly Configured', hint=( f'Failed to import "{path}" defined in ' '"OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN". ' 'Make sure you have provided a valid dotted path.' ), obj='Settings', ) ) except AssertionError: errors.append( checks.Error( msg='Improperly Configured', hint=( f'"{path}" does not subclasses ' '"django.contrib.admin.ModelAdmin". Only derivatives ' 'ModelAdmin can be added in "OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN".' ), obj='Settings', ) ) return errors
def check_AUTOLOAD_TRANSLATIONS(value): errors = [] if not isinstance(value, bool): errors.append( checks.Warning( 'HVAD["AUTOLOAD_TRANSLATIONS"] should be True or False', obj='AUTOLOAD_TRANSLATIONS', id='hvad.settings.W02')) return errors
def check_jpeg_legible(jpeg_quality, eid, **kwargs): errors = [] if (jpeg_quality and (jpeg_quality < 12)): errors.append( checks.Warning( "'jpeg_quality' value '{}' is very low.".format(jpeg_quality), id=eid, )) return errors
def check_USE_DEFAULT_QUERYSET(value): errors = [] if not isinstance(value, bool): errors.append( checks.Warning( 'HVAD["USE_DEFAULT_QUERYSET"] should be True or False', obj='USE_DEFAULT_QUERYSET', id='hvad.settings.W03')) return errors
def check_user_model(**kwargs): errors = [] cls = apps.get_model(settings.AUTH_USER_MODEL) # Check that REQUIRED_FIELDS is a list if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)): errors.append( checks.Error( "'REQUIRED_FIELDS' must be a list or tuple.", hint=None, obj=cls, id='auth.E001', ) ) # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS. if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS: errors.append( checks.Error( ("The field named as the 'USERNAME_FIELD' " "for a custom user model must not be included in 'REQUIRED_FIELDS'."), hint=None, obj=cls, id='auth.E002', ) ) # Check that the username field is unique if not cls._meta.get_field(cls.USERNAME_FIELD).unique: if (settings.AUTHENTICATION_BACKENDS == ('myrobogals.auth.backends.ModelBackend',)): errors.append( checks.Error( "'%s.%s' must be unique because it is named as the 'USERNAME_FIELD'." % ( cls._meta.object_name, cls.USERNAME_FIELD ), hint=None, obj=cls, id='auth.E003', ) ) else: errors.append( checks.Warning( "'%s.%s' is named as the 'USERNAME_FIELD', but it is not unique." % ( cls._meta.object_name, cls.USERNAME_FIELD ), hint=('Ensure that your authentication backend(s) can handle ' 'non-unique usernames.'), obj=cls, id='auth.W004', ) ) return errors
def test_checking_search_fields(self): with patch_search_fields(models.Book, models.Book.search_fields + [index.SearchField('foo')]): expected_errors = [ checks.Warning( "Book.search_fields contains field 'foo' but it doesn't exist", obj=models.Book ) ] errors = models.Book.check() self.assertEqual(errors, expected_errors)