def test_models_py(self): """ Tests that the models in the models.py file were loaded correctly. """ self.assertEqual(app_cache.get_model("app_cache", "TotallyNormal"), TotallyNormal) self.assertEqual(app_cache.get_model("app_cache", "SoAlternative"), None) self.assertEqual(new_app_cache.get_model("app_cache", "TotallyNormal"), None) self.assertEqual(new_app_cache.get_model("app_cache", "SoAlternative"), SoAlternative)
def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs): try: app_cache.get_model('auth', 'Permission') except UnavailableApp: return if not router.allow_migrate(db, auth_app.Permission): return from django.contrib.contenttypes.models import ContentType app_models = app_cache.get_models(app) # This will hold the permissions we're looking for as # (content_type, (codename, name)) searched_perms = list() # The codenames and ctypes that should exist. ctypes = set() for klass in app_models: # Force looking up the content types in the current database # before creating foreign keys to them. ctype = ContentType.objects.db_manager(db).get_for_model(klass) ctypes.add(ctype) for perm in _get_all_permissions(klass._meta, ctype): searched_perms.append((ctype, perm)) # Find all the Permissions that have a content_type for a model we're # looking for. We don't need to check for codenames since we already have # a list of the ones we're going to create. all_perms = set(auth_app.Permission.objects.using(db).filter( content_type__in=ctypes, ).values_list( "content_type", "codename" )) perms = [ auth_app.Permission(codename=codename, name=name, content_type=ctype) for ctype, (codename, name) in searched_perms if (ctype.pk, codename) not in all_perms ] # Validate the permissions before bulk_creation to avoid cryptic # database error when the verbose_name is longer than 50 characters permission_name_max_length = auth_app.Permission._meta.get_field('name').max_length verbose_name_max_length = permission_name_max_length - 11 # len('Can change ') prefix for perm in perms: if len(perm.name) > permission_name_max_length: raise exceptions.ValidationError( "The verbose_name of %s is longer than %s characters" % ( perm.content_type, verbose_name_max_length, ) ) auth_app.Permission.objects.using(db).bulk_create(perms) if verbosity >= 2: for perm in perms: print("Adding permission '%s'" % perm)
def test_missing_app(self): """ Test that repeated app loading doesn't succeed in case there is an error. Refs #17667. """ app_cache = AppCache() # Pretend we're the master app cache to test populate(). app_cache.master = True with override_settings(INSTALLED_APPS=('notexists',)): with self.assertRaises(ImportError): app_cache.get_model('notexists', 'nomodel', seed_cache=True) with self.assertRaises(ImportError): app_cache.get_model('notexists', 'nomodel', seed_cache=True)
def _get_model(model_identifier): """ Helper to look up a model from an "app_label.model_name" string. """ try: Model = app_cache.get_model(*model_identifier.split(".")) except TypeError: Model = None if Model is None: raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier) return Model
def model_unpickle(model_id, attrs, factory): """ Used to unpickle Model subclasses with deferred fields. """ if isinstance(model_id, tuple): model = app_cache.get_model(*model_id) else: # Backwards compat - the model was cached directly in earlier versions. model = model_id cls = factory(model, attrs) return cls.__new__(cls)
def create_superuser(app, created_models, verbosity, db, **kwargs): try: app_cache.get_model('auth', 'Permission') UserModel = get_user_model() except UnavailableApp: return from django.core.management import call_command if UserModel in created_models and kwargs.get('interactive', True): msg = ("\nYou just installed Django's auth system, which means you " "don't have any superusers defined.\nWould you like to create one " "now? (yes/no): ") confirm = input(msg) while 1: if confirm not in ('yes', 'no'): confirm = input('Please enter either "yes" or "no": ') continue if confirm == 'yes': call_command("createsuperuser", interactive=True, database=db) break
def get_user_model(): """ Returns the User model that is active in this project. """ from django.core.apps import app_cache try: app_label, model_name = settings.AUTH_USER_MODEL.split('.') except ValueError: raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'") user_model = app_cache.get_model(app_label, model_name) if user_model is None: raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL) return user_model
def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS): """ This view generates KML for the given app label, model, and field name. The model's default manager must be GeoManager, and the field name must be that of a geographic field. """ placemarks = [] klass = app_cache.get_model(label, model) if not klass: raise Http404('You must supply a valid app label and module name. Got "%s.%s"' % (label, model)) if field_name: try: field, _, _, _ = klass._meta.get_field_by_name(field_name) if not isinstance(field, GeometryField): raise FieldDoesNotExist except FieldDoesNotExist: raise Http404('Invalid geometry field.') connection = connections[using] if connection.ops.postgis: # PostGIS will take care of transformation. placemarks = klass._default_manager.using(using).kml(field_name=field_name) else: # There's no KML method on Oracle or MySQL, so we use the `kml` # attribute of the lazy geometry instead. placemarks = [] if connection.ops.oracle: qs = klass._default_manager.using(using).transform(4326, field_name=field_name) else: qs = klass._default_manager.using(using).all() for mod in qs: mod.kml = getattr(mod, field_name).kml placemarks.append(mod) # Getting the render function and rendering to the correct. if compress: render = render_to_kmz else: render = render_to_kml return render('gis/kml/placemarks.kml', {'places': placemarks})
def _get_model_from_node(self, node, attr): """ Helper to look up a model from a <object model=...> or a <field rel=... to=...> node. """ model_identifier = node.getAttribute(attr) if not model_identifier: raise base.DeserializationError( "<%s> node is missing the required '%s' attribute" % (node.nodeName, attr)) try: Model = app_cache.get_model(*model_identifier.split(".")) except TypeError: Model = None if Model is None: raise base.DeserializationError( "<%s> node has invalid model identifier: '%s'" % (node.nodeName, model_identifier)) return Model
def model_class(self): "Returns the Python model class for this type of content." return app_cache.get_model(self.app_label, self.model, only_installed=False)
def get_context_data(self, **kwargs): # Get the model class. try: app_cache.get_app_config(self.kwargs['app_label']) except LookupError: raise Http404(_("App %(app_label)r not found") % self.kwargs) model = app_cache.get_model(self.kwargs['app_label'], self.kwargs['model_name']) if model is None: raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % self.kwargs) opts = model._meta # Gather fields/field descriptions. fields = [] for field in opts.fields: # ForeignKey is a special case since the field will actually be a # descriptor that returns the other object if isinstance(field, models.ForeignKey): data_type = field.rel.to.__name__ app_label = field.rel.to._meta.app_label verbose = utils.parse_rst( (_("the related `%(app_label)s.%(data_type)s` object") % { 'app_label': app_label, 'data_type': data_type, }), 'model', _('model:') + data_type, ) else: data_type = get_readable_field_data_type(field) verbose = field.verbose_name fields.append({ 'name': field.name, 'data_type': data_type, 'verbose': verbose, 'help_text': field.help_text, }) # Gather many-to-many fields. for field in opts.many_to_many: data_type = field.rel.to.__name__ app_label = field.rel.to._meta.app_label verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': app_label, 'object_name': data_type} fields.append({ 'name': "%s.all" % field.name, "data_type": 'List', 'verbose': utils.parse_rst(_("all %s") % verbose, 'model', _('model:') + opts.model_name), }) fields.append({ 'name': "%s.count" % field.name, 'data_type': 'Integer', 'verbose': utils.parse_rst(_("number of %s") % verbose, 'model', _('model:') + opts.model_name), }) # Gather model methods. for func_name, func in model.__dict__.items(): if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1): try: for exclude in MODEL_METHODS_EXCLUDE: if func_name.startswith(exclude): raise StopIteration except StopIteration: continue verbose = func.__doc__ if verbose: verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.model_name) fields.append({ 'name': func_name, 'data_type': get_return_data_type(func_name), 'verbose': verbose, }) # Gather related objects for rel in opts.get_all_related_objects() + opts.get_all_related_many_to_many_objects(): verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': rel.opts.app_label, 'object_name': rel.opts.object_name} accessor = rel.get_accessor_name() fields.append({ 'name': "%s.all" % accessor, 'data_type': 'List', 'verbose': utils.parse_rst(_("all %s") % verbose, 'model', _('model:') + opts.model_name), }) fields.append({ 'name': "%s.count" % accessor, 'data_type': 'Integer', 'verbose': utils.parse_rst(_("number of %s") % verbose, 'model', _('model:') + opts.model_name), }) kwargs.update({ 'name': '%s.%s' % (opts.app_label, opts.object_name), # Translators: %s is an object type name 'summary': _("Attributes on %s objects") % opts.object_name, 'description': model.__doc__, 'fields': fields, }) return super(ModelDetailView, self).get_context_data(**kwargs)
def handle(self, *app_labels, **options): from django.core.apps import app_cache format = options.get('format') indent = options.get('indent') using = options.get('database') excludes = options.get('exclude') show_traceback = options.get('traceback') use_natural_keys = options.get('use_natural_keys') if use_natural_keys: warnings.warn("``--natural`` is deprecated; use ``--natural-foreign`` instead.", PendingDeprecationWarning) use_natural_foreign_keys = options.get('use_natural_foreign_keys') or use_natural_keys use_natural_primary_keys = options.get('use_natural_primary_keys') use_base_manager = options.get('use_base_manager') pks = options.get('primary_keys') if pks: primary_keys = pks.split(',') else: primary_keys = [] excluded_apps = set() excluded_models = set() for exclude in excludes: if '.' in exclude: app_label, model_name = exclude.split('.', 1) model_obj = app_cache.get_model(app_label, model_name) if not model_obj: raise CommandError('Unknown model in excludes: %s' % exclude) excluded_models.add(model_obj) else: try: app_obj = app_cache.get_app_config(exclude).models_module if app_obj is not None: excluded_apps.add(app_obj) except LookupError: raise CommandError('Unknown app in excludes: %s' % exclude) if len(app_labels) == 0: if primary_keys: raise CommandError("You can only use --pks option with one model") app_list = OrderedDict((app_config.models_module, None) for app_config in app_cache.get_app_configs(only_with_models_module=True) if app_config.models_module not in excluded_apps) else: if len(app_labels) > 1 and primary_keys: raise CommandError("You can only use --pks option with one model") app_list = OrderedDict() for label in app_labels: try: app_label, model_label = label.split('.') try: app = app_cache.get_app_config(app_label).models_module except LookupError: raise CommandError("Unknown application: %s" % app_label) if app is None or app in excluded_apps: continue model = app_cache.get_model(app_label, model_label) if model is None: raise CommandError("Unknown model: %s.%s" % (app_label, model_label)) if app in app_list.keys(): if app_list[app] and model not in app_list[app]: app_list[app].append(model) else: app_list[app] = [model] except ValueError: if primary_keys: raise CommandError("You can only use --pks option with one model") # This is just an app - no model qualifier app_label = label try: app = app_cache.get_app_config(app_label).models_module except LookupError: raise CommandError("Unknown application: %s" % app_label) if app is None or app in excluded_apps: continue app_list[app] = None # Check that the serialization format exists; this is a shortcut to # avoid collating all the objects and _then_ failing. if format not in serializers.get_public_serializer_formats(): try: serializers.get_serializer(format) except serializers.SerializerDoesNotExist: pass raise CommandError("Unknown serialization format: %s" % format) def get_objects(): # Collate the objects to be serialized. for model in sort_dependencies(app_list.items()): if model in excluded_models: continue if not model._meta.proxy and router.allow_migrate(using, model): if use_base_manager: objects = model._base_manager else: objects = model._default_manager queryset = objects.using(using).order_by(model._meta.pk.name) if primary_keys: queryset = queryset.filter(pk__in=primary_keys) for obj in queryset.iterator(): yield obj try: self.stdout.ending = None serializers.serialize(format, get_objects(), indent=indent, use_natural_foreign_keys=use_natural_foreign_keys, use_natural_primary_keys=use_natural_primary_keys, stream=self.stdout) except Exception as e: if show_traceback: raise raise CommandError("Unable to serialize database: %s" % e)
def sort_dependencies(app_list): """Sort a list of app,modellist pairs into a single list of models. The single list of models is sorted so that any model with a natural key is serialized before a normal model, and any model with a natural key dependency has it's dependencies serialized first. """ from django.core.apps import app_cache # Process the list of models, and get the list of dependencies model_dependencies = [] models = set() for app, model_list in app_list: if model_list is None: model_list = app_cache.get_models(app) for model in model_list: models.add(model) # Add any explicitly defined dependencies if hasattr(model, 'natural_key'): deps = getattr(model.natural_key, 'dependencies', []) if deps: deps = [app_cache.get_model(*d.split('.')) for d in deps] else: deps = [] # Now add a dependency for any FK or M2M relation with # a model that defines a natural key for field in model._meta.fields: if hasattr(field.rel, 'to'): rel_model = field.rel.to if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) for field in model._meta.many_to_many: rel_model = field.rel.to if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) model_dependencies.append((model, deps)) model_dependencies.reverse() # Now sort the models to ensure that dependencies are met. This # is done by repeatedly iterating over the input list of models. # If all the dependencies of a given model are in the final list, # that model is promoted to the end of the final list. This process # continues until the input list is empty, or we do a full iteration # over the input models without promoting a model to the final list. # If we do a full iteration without a promotion, that means there are # circular dependencies in the list. model_list = [] while model_dependencies: skipped = [] changed = False while model_dependencies: model, deps = model_dependencies.pop() # If all of the models in the dependency list are either already # on the final model list, or not on the original serialization list, # then we've found another model with all it's dependencies satisfied. found = True for candidate in ((d not in models or d in model_list) for d in deps): if not candidate: found = False if found: model_list.append(model) changed = True else: skipped.append((model, deps)) if not changed: raise CommandError("Can't resolve dependencies for %s in serialized app list." % ', '.join('%s.%s' % (model._meta.app_label, model._meta.object_name) for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__)) ) model_dependencies = skipped return model_list
def post_comment(request, next=None, using=None): """ Post a comment. HTTP POST is required. If ``POST['submit'] == "preview"`` or if there are errors a preview template, ``comments/preview.html``, will be rendered. """ # Fill out some initial data fields from an authenticated user, if present data = request.POST.copy() if request.user.is_authenticated(): if not data.get('name', ''): data["name"] = request.user.get_full_name() or request.user.get_username() if not data.get('email', ''): data["email"] = request.user.email # Look up the object we're trying to comment about ctype = data.get("content_type") object_pk = data.get("object_pk") if ctype is None or object_pk is None: return CommentPostBadRequest("Missing content_type or object_pk field.") try: model = app_cache.get_model(*ctype.split(".", 1)) target = model._default_manager.using(using).get(pk=object_pk) except TypeError: return CommentPostBadRequest( "Invalid content_type value: %r" % escape(ctype)) except AttributeError: return CommentPostBadRequest( "The given content-type %r does not resolve to a valid model." % \ escape(ctype)) except ObjectDoesNotExist: return CommentPostBadRequest( "No object matching content-type %r and object PK %r exists." % \ (escape(ctype), escape(object_pk))) except (ValueError, ValidationError) as e: return CommentPostBadRequest( "Attempting go get content-type %r and object PK %r exists raised %s" % \ (escape(ctype), escape(object_pk), e.__class__.__name__)) # Do we want to preview the comment? preview = "preview" in data # Construct the comment form form = comments.get_form()(target, data=data) # Check security information if form.security_errors(): return CommentPostBadRequest( "The comment form failed security verification: %s" % \ escape(str(form.security_errors()))) # If there are errors or if we requested a preview show the comment if form.errors or preview: template_list = [ # These first two exist for purely historical reasons. # Django v1.0 and v1.1 allowed the underscore format for # preview templates, so we have to preserve that format. "comments/%s_%s_preview.html" % (model._meta.app_label, model._meta.model_name), "comments/%s_preview.html" % model._meta.app_label, # Now the usual directory based template hierarchy. "comments/%s/%s/preview.html" % (model._meta.app_label, model._meta.model_name), "comments/%s/preview.html" % model._meta.app_label, "comments/preview.html", ] return render_to_response( template_list, { "comment": form.data.get("comment", ""), "form": form, "next": data.get("next", next), }, RequestContext(request, {}) ) # Otherwise create the comment comment = form.get_comment_object() comment.ip_address = request.META.get("REMOTE_ADDR", None) if request.user.is_authenticated(): comment.user = request.user # Signal that the comment is about to be saved responses = signals.comment_will_be_posted.send( sender=comment.__class__, comment=comment, request=request ) for (receiver, response) in responses: if response == False: return CommentPostBadRequest( "comment_will_be_posted receiver %r killed the comment" % receiver.__name__) # Save the comment and signal that it was saved comment.save() signals.comment_was_posted.send( sender=comment.__class__, comment=comment, request=request ) return next_redirect(request, fallback=next or 'comments-comment-done', c=comment._get_pk_val())
def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, **kwargs): """ Creates content types for models in the given app, removing any model entries that no longer have a matching model class. """ try: app_cache.get_model('contenttypes', 'ContentType') except UnavailableApp: return if not router.allow_migrate(db, ContentType): return ContentType.objects.clear_cache() app_models = app_cache.get_models(app) if not app_models: return # They all have the same app_label, get the first one. app_label = app_models[0]._meta.app_label app_models = dict( (model._meta.model_name, model) for model in app_models ) # Get all the content types content_types = dict( (ct.model, ct) for ct in ContentType.objects.using(db).filter(app_label=app_label) ) to_remove = [ ct for (model_name, ct) in six.iteritems(content_types) if model_name not in app_models ] cts = [ ContentType( name=smart_text(model._meta.verbose_name_raw), app_label=app_label, model=model_name, ) for (model_name, model) in six.iteritems(app_models) if model_name not in content_types ] ContentType.objects.using(db).bulk_create(cts) if verbosity >= 2: for ct in cts: print("Adding content type '%s | %s'" % (ct.app_label, ct.model)) # Confirm that the content type is stale before deletion. if to_remove: if kwargs.get('interactive', False): content_type_display = '\n'.join( ' %s | %s' % (ct.app_label, ct.model) for ct in to_remove ) ok_to_delete = input("""The following content types are stale and need to be deleted: %s Any objects related to these content types by a foreign key will also be deleted. Are you sure you want to delete these content types? If you're unsure, answer 'no'. Type 'yes' to continue, or 'no' to cancel: """ % content_type_display) else: ok_to_delete = False if ok_to_delete == 'yes': for ct in to_remove: if verbosity >= 2: print("Deleting stale content type '%s | %s'" % (ct.app_label, ct.model)) ct.delete() else: if verbosity >= 2: print("Stale content types remain.")
def test_get_model_with_not_installed(self): self.assertEqual( app_cache.get_model( "not_installed", "NotInstalledModel", only_installed=False), self.not_installed_module.NotInstalledModel)
def test_get_model_only_returns_installed_models(self): self.assertEqual( app_cache.get_model("not_installed", "NotInstalledModel"), None)
def get_validation_errors(outfile, app=None): """ Validates all models that are part of the specified app. If no app name is provided, validates all models of all installed apps. Writes errors, if any, to outfile. Returns number of errors. """ from django.core.apps import app_cache from django.db import connection, models from django.db.models.deletion import SET_NULL, SET_DEFAULT e = ModelErrorCollection(outfile) for cls in app_cache.get_models(app, include_swapped=True): opts = cls._meta # Check swappable attribute. if opts.swapped: try: app_label, model_name = opts.swapped.split('.') except ValueError: e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable) continue if not app_cache.get_model(app_label, model_name): e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped) # No need to perform any other validation checks on a swapped model. continue # If this is the current User model, check known validation problems with User models if settings.AUTH_USER_MODEL == '%s.%s' % (opts.app_label, opts.object_name): # Check that REQUIRED_FIELDS is a list if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)): e.add(opts, 'The REQUIRED_FIELDS must be a list or tuple.') # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS. if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS: e.add(opts, 'The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.') # Check that the username field is unique if not opts.get_field(cls.USERNAME_FIELD).unique: e.add(opts, 'The USERNAME_FIELD must be unique. Add unique=True to the field parameters.') # Store a list of column names which have already been used by other fields. used_column_names = [] # Model isn't swapped; do field-specific validation. for f in opts.local_fields: if f.name == 'id' and not f.primary_key and opts.pk.name == 'id': e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name) if f.name.endswith('_'): e.add(opts, '"%s": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.' % f.name) if (f.primary_key and f.null and not connection.features.interprets_empty_strings_as_nulls): # We cannot reliably check this for backends like Oracle which # consider NULL and '' to be equal (and thus set up # character-based fields a little differently). e.add(opts, '"%s": Primary key fields cannot have null=True.' % f.name) # Column name validation. # Determine which column name this field wants to use. _, column_name = f.get_attname_column() # Ensure the column name is not already in use. if column_name and column_name in used_column_names: e.add(opts, "Field '%s' has column name '%s' that is already used." % (f.name, column_name)) else: used_column_names.append(column_name) if isinstance(f, models.CharField): try: max_length = int(f.max_length) if max_length <= 0: e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name) except (ValueError, TypeError): e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name) if isinstance(f, models.DecimalField): decimalp_ok, mdigits_ok = False, False decimalp_msg = '"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.' try: decimal_places = int(f.decimal_places) if decimal_places < 0: e.add(opts, decimalp_msg % f.name) else: decimalp_ok = True except (ValueError, TypeError): e.add(opts, decimalp_msg % f.name) mdigits_msg = '"%s": DecimalFields require a "max_digits" attribute that is a positive integer.' try: max_digits = int(f.max_digits) if max_digits <= 0: e.add(opts, mdigits_msg % f.name) else: mdigits_ok = True except (ValueError, TypeError): e.add(opts, mdigits_msg % f.name) invalid_values_msg = '"%s": DecimalFields require a "max_digits" attribute value that is greater than or equal to the value of the "decimal_places" attribute.' if decimalp_ok and mdigits_ok: if decimal_places > max_digits: e.add(opts, invalid_values_msg % f.name) if isinstance(f, models.ImageField): try: from django.utils.image import Image # NOQA except ImportError: e.add(opts, '"%s": To use ImageFields, you need to install Pillow. Get it at https://pypi.python.org/pypi/Pillow.' % f.name) if isinstance(f, models.BooleanField) and getattr(f, 'null', False): e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name) if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders): e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name) if isinstance(f, models.GenericIPAddressField) and not getattr(f, 'null', False) and getattr(f, 'blank', False): e.add(opts, '"%s": GenericIPAddressField can not accept blank values if null values are not allowed, as blank values are stored as null.' % f.name) if f.choices: if isinstance(f.choices, six.string_types) or not is_iterable(f.choices): e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name) else: for c in f.choices: if isinstance(c, six.string_types) or not is_iterable(c) or len(c) != 2: e.add(opts, '"%s": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).' % f.name) if f.db_index not in (None, True, False): e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name) # Perform any backend-specific field validation. connection.validation.validate_field(e, opts, f) # Check if the on_delete behavior is sane if f.rel and hasattr(f.rel, 'on_delete'): if f.rel.on_delete == SET_NULL and not f.null: e.add(opts, "'%s' specifies on_delete=SET_NULL, but cannot be null." % f.name) elif f.rel.on_delete == SET_DEFAULT and not f.has_default(): e.add(opts, "'%s' specifies on_delete=SET_DEFAULT, but has no default value." % f.name) # Check to see if the related field will clash with any existing # fields, m2m fields, m2m related objects or related objects if f.rel: if f.rel.to not in app_cache.get_models(): # If the related model is swapped, provide a hint; # otherwise, the model just hasn't been installed. if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped: e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable)) else: e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to)) # it is a string and we could not find the model it refers to # so skip the next section if isinstance(f.rel.to, six.string_types): continue # Make sure the related field specified by a ForeignKey is unique if f.requires_unique_target: if len(f.foreign_related_fields) > 1: has_unique_field = False for rel_field in f.foreign_related_fields: has_unique_field = has_unique_field or rel_field.unique if not has_unique_field: e.add(opts, "Field combination '%s' under model '%s' must have a unique=True constraint" % (','.join(rel_field.name for rel_field in f.foreign_related_fields), f.rel.to.__name__)) else: if not f.foreign_related_fields[0].unique: e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.foreign_related_fields[0].name, f.rel.to.__name__)) rel_opts = f.rel.to._meta rel_name = f.related.get_accessor_name() rel_query_name = f.related_query_name() if not f.rel.is_hidden(): for r in rel_opts.fields: if r.name == rel_name: e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) if r.name == rel_query_name: e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.local_many_to_many: if r.name == rel_name: e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) if r.name == rel_query_name: e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.get_all_related_many_to_many_objects(): if r.get_accessor_name() == rel_name: e.add(opts, "Accessor for field '%s' clashes with accessor for field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) if r.get_accessor_name() == rel_query_name: e.add(opts, "Reverse query name for field '%s' clashes with accessor for field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) for r in rel_opts.get_all_related_objects(): if r.field is not f: if r.get_accessor_name() == rel_name: e.add(opts, "Accessor for field '%s' clashes with accessor for field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) if r.get_accessor_name() == rel_query_name: e.add(opts, "Reverse query name for field '%s' clashes with accessor for field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) seen_intermediary_signatures = [] for i, f in enumerate(opts.local_many_to_many): # Check to see if the related m2m field will clash with any # existing fields, m2m fields, m2m related objects or related # objects if f.rel.to not in app_cache.get_models(): # If the related model is swapped, provide a hint; # otherwise, the model just hasn't been installed. if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped: e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable)) else: e.add(opts, "'%s' has an m2m relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to)) # it is a string and we could not find the model it refers to # so skip the next section if isinstance(f.rel.to, six.string_types): continue # Check that the field is not set to unique. ManyToManyFields do not support unique. if f.unique: e.add(opts, "ManyToManyFields cannot be unique. Remove the unique argument on '%s'." % f.name) if f.rel.through is not None and not isinstance(f.rel.through, six.string_types): from_model, to_model = cls, f.rel.to if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created: e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.") seen_from, seen_to, seen_self = False, False, 0 for inter_field in f.rel.through._meta.fields: rel_to = getattr(inter_field.rel, 'to', None) if from_model == to_model: # relation to self if rel_to == from_model: seen_self += 1 if seen_self > 2: e.add(opts, "Intermediary model %s has more than " "two foreign keys to %s, which is ambiguous " "and is not permitted." % ( f.rel.through._meta.object_name, from_model._meta.object_name ) ) else: if rel_to == from_model: if seen_from: e.add(opts, "Intermediary model %s has more " "than one foreign key to %s, which is " "ambiguous and is not permitted." % ( f.rel.through._meta.object_name, from_model._meta.object_name ) ) else: seen_from = True elif rel_to == to_model: if seen_to: e.add(opts, "Intermediary model %s has more " "than one foreign key to %s, which is " "ambiguous and is not permitted." % ( f.rel.through._meta.object_name, rel_to._meta.object_name ) ) else: seen_to = True if f.rel.through not in app_cache.get_models(include_auto_created=True): e.add(opts, "'%s' specifies an m2m relation through model " "%s, which has not been installed." % (f.name, f.rel.through)) signature = (f.rel.to, cls, f.rel.through) if signature in seen_intermediary_signatures: e.add(opts, "The model %s has two manually-defined m2m " "relations through the model %s, which is not " "permitted. Please consider using an extra field on " "your intermediary model instead." % ( cls._meta.object_name, f.rel.through._meta.object_name ) ) else: seen_intermediary_signatures.append(signature) if not f.rel.through._meta.auto_created: seen_related_fk, seen_this_fk = False, False for field in f.rel.through._meta.fields: if field.rel: if not seen_related_fk and field.rel.to == f.rel.to: seen_related_fk = True elif field.rel.to == cls: seen_this_fk = True if not seen_related_fk or not seen_this_fk: e.add(opts, "'%s' is a manually-defined m2m relation " "through model %s, which does not have foreign keys " "to %s and %s" % ( f.name, f.rel.through._meta.object_name, f.rel.to._meta.object_name, cls._meta.object_name ) ) elif isinstance(f.rel.through, six.string_types): e.add(opts, "'%s' specifies an m2m relation through model %s, " "which has not been installed" % (f.name, f.rel.through)) rel_opts = f.rel.to._meta rel_name = f.related.get_accessor_name() rel_query_name = f.related_query_name() # If rel_name is none, there is no reverse accessor (this only # occurs for symmetrical m2m relations to self). If this is the # case, there are no clashes to check for this field, as there are # no reverse descriptors for this field. if not f.rel.is_hidden(): for r in rel_opts.fields: if r.name == rel_name: e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) if r.name == rel_query_name: e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.local_many_to_many: if r.name == rel_name: e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) if r.name == rel_query_name: e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.get_all_related_many_to_many_objects(): if r.field is not f: if r.get_accessor_name() == rel_name: e.add(opts, "Accessor for m2m field '%s' clashes with accessor for m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) if r.get_accessor_name() == rel_query_name: e.add(opts, "Reverse query name for m2m field '%s' clashes with accessor for m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) for r in rel_opts.get_all_related_objects(): if r.get_accessor_name() == rel_name: e.add(opts, "Accessor for m2m field '%s' clashes with accessor for field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) if r.get_accessor_name() == rel_query_name: e.add(opts, "Reverse query name for m2m field '%s' clashes with accessor for field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, r.model._meta.object_name, r.field.name, f.name)) # Check ordering attribute. if opts.ordering: for field_name in opts.ordering: if field_name == '?': continue if field_name.startswith('-'): field_name = field_name[1:] if opts.order_with_respect_to and field_name == '_order': continue # Skip ordering in the format field1__field2 (FIXME: checking # this format would be nice, but it's a little fiddly). if '__' in field_name: continue # Skip ordering on pk. This is always a valid order_by field # but is an alias and therefore won't be found by opts.get_field. if field_name == 'pk': continue try: opts.get_field(field_name, many_to_many=False) except models.FieldDoesNotExist: e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name) # Check unique_together. for ut in opts.unique_together: validate_local_fields(e, opts, "unique_together", ut) if not isinstance(opts.index_together, collections.Sequence): e.add(opts, '"index_together" must a sequence') else: for it in opts.index_together: validate_local_fields(e, opts, "index_together", it) validate_model_signals(e) return len(e.errors)