model字段对象和forms字段对象的区别和联系
Posted shenjianping
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了model字段对象和forms字段对象的区别和联系相关的知识,希望对你有一定的参考价值。
一、model字段对象
(一)_meta
_meta是django.db.models.options.Options的实例,获取字段对象可通过模型类来进行获取,而_meta可提供如下功能:
- 获取模型的所有字段实例
- 通过名称获取模型的单个字段实例
1、获取模型的单个字段实例
In [19]: models.Book._meta.get_field("title") Out[19]: <django.db.models.fields.CharField: title>
2、获取模型的所有字段实例
In [20]: models.Book._meta.get_fields() Out[20]: (<django.db.models.fields.AutoField: id>, <django.db.models.fields.CharField: title>, <django.db.models.fields.IntegerField: price>, <django.db.models.fields.DateField: pub_date>, <django.db.models.fields.related.ForeignKey: publish>, <django.db.models.fields.related.ManyToManyField: authors>)
3、其它
当然,_meta中还有其它属性或者方法可用:
class Options: FORWARD_PROPERTIES = ‘fields‘, ‘many_to_many‘, ‘concrete_fields‘, ‘local_concrete_fields‘, ‘_forward_fields_map‘, ‘managers‘, ‘managers_map‘, ‘base_manager‘, ‘default_manager‘, REVERSE_PROPERTIES = ‘related_objects‘, ‘fields_map‘, ‘_relation_tree‘ default_apps = apps def __init__(self, meta, app_label=None): self._get_fields_cache = self.local_fields = [] self.local_many_to_many = [] self.private_fields = [] self.local_managers = [] self.base_manager_name = None self.default_manager_name = None self.model_name = None self.verbose_name = None self.verbose_name_plural = None self.db_table = ‘‘ self.ordering = [] self._ordering_clash = False self.indexes = [] self.unique_together = [] self.index_together = [] self.select_on_save = False self.default_permissions = (‘add‘, ‘change‘, ‘delete‘) self.permissions = [] self.object_name = None self.app_label = app_label self.get_latest_by = None self.order_with_respect_to = None self.db_tablespace = settings.DEFAULT_TABLESPACE self.required_db_features = [] self.required_db_vendor = None self.meta = meta self.pk = None self.auto_field = None self.abstract = False self.managed = True self.proxy = False # For any class that is a proxy (including automatically created # classes for deferred object loading), proxy_for_model tells us # which class this model is proxying. Note that proxy_for_model # can create a chain of proxy models. For non-proxy models, the # variable is always None. self.proxy_for_model = None # For any non-abstract class, the concrete class is the model # in the end of the proxy_for_model chain. In particular, for # concrete models, the concrete_model is always the class itself. self.concrete_model = None self.swappable = None self.parents = OrderedDict() self.auto_created = False # List of all lookups defined in ForeignKey ‘limit_choices_to‘ options # from *other* models. Needed for some admin checks. Internal use only. self.related_fkey_lookups = [] # A custom app registry to use, if you‘re making a separate model set. self.apps = self.default_apps self.default_related_name = None @property def label(self): return ‘%s.%s‘ % (self.app_label, self.object_name) @property def label_lower(self): return ‘%s.%s‘ % (self.app_label, self.model_name) @property def app_config(self): # Don‘t go through get_app_config to avoid triggering imports. return self.apps.app_configs.get(self.app_label) @property def installed(self): return self.app_config is not None def contribute_to_class(self, cls, name): from django.db import connection from django.db.backends.utils import truncate_name cls._meta = self self.model = cls # First, construct the default values for these options. self.object_name = cls.__name__ self.model_name = self.object_name.lower() self.verbose_name = camel_case_to_spaces(self.object_name) # Store the original user-defined values for each option, # for use when serializing the model definition self.original_attrs = # Next, apply any overridden values from ‘class Meta‘. if self.meta: meta_attrs = self.meta.__dict__.copy() for name in self.meta.__dict__: # Ignore any private attributes that Django doesn‘t care about. # NOTE: We can‘t modify a dictionary‘s contents while looping # over it, so we loop over the *original* dictionary instead. if name.startswith(‘_‘): del meta_attrs[name] for attr_name in DEFAULT_NAMES: if attr_name in meta_attrs: setattr(self, attr_name, meta_attrs.pop(attr_name)) self.original_attrs[attr_name] = getattr(self, attr_name) elif hasattr(self.meta, attr_name): setattr(self, attr_name, getattr(self.meta, attr_name)) self.original_attrs[attr_name] = getattr(self, attr_name) self.unique_together = normalize_together(self.unique_together) self.index_together = normalize_together(self.index_together) # verbose_name_plural is a special case because it uses a ‘s‘ # by default. if self.verbose_name_plural is None: self.verbose_name_plural = format_lazy(‘s‘, self.verbose_name) # order_with_respect_and ordering are mutually exclusive. self._ordering_clash = bool(self.ordering and self.order_with_respect_to) # Any leftover attributes must be invalid. if meta_attrs != : raise TypeError("‘class Meta‘ got invalid attribute(s): %s" % ‘,‘.join(meta_attrs)) else: self.verbose_name_plural = format_lazy(‘s‘, self.verbose_name) del self.meta # If the db_table wasn‘t provided, use the app_label + model_name. if not self.db_table: self.db_table = "%s_%s" % (self.app_label, self.model_name) self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) def _prepare(self, model): if self.order_with_respect_to: # The app registry will not be ready at this point, so we cannot # use get_field(). query = self.order_with_respect_to try: self.order_with_respect_to = next( f for f in self._get_fields(reverse=False) if f.name == query or f.attname == query ) except StopIteration: raise FieldDoesNotExist("%s has no field named ‘%s‘" % (self.object_name, query)) self.ordering = (‘_order‘,) if not any(isinstance(field, OrderWrt) for field in model._meta.local_fields): model.add_to_class(‘_order‘, OrderWrt()) else: self.order_with_respect_to = None if self.pk is None: if self.parents: # Promote the first parent link in lieu of adding yet another # field. field = next(iter(self.parents.values())) # Look for a local field with the same name as the # first parent link. If a local field has already been # created, use it instead of promoting the parent already_created = [fld for fld in self.local_fields if fld.name == field.name] if already_created: field = already_created[0] field.primary_key = True self.setup_pk(field) if not field.remote_field.parent_link: raise ImproperlyConfigured( ‘Add parent_link=True to %s.‘ % field, ) else: auto = AutoField(verbose_name=‘ID‘, primary_key=True, auto_created=True) model.add_to_class(‘id‘, auto) def add_manager(self, manager): self.local_managers.append(manager) self._expire_cache() def add_field(self, field, private=False): # Insert the given field in the order in which it was created, using # the "creation_counter" attribute of the field. # Move many-to-many related fields from self.fields into # self.many_to_many. if private: self.private_fields.append(field) elif field.is_relation and field.many_to_many: self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field) else: self.local_fields.insert(bisect(self.local_fields, field), field) self.setup_pk(field) # If the field being added is a relation to another known field, # expire the cache on this field and the forward cache on the field # being referenced, because there will be new relationships in the # cache. Otherwise, expire the cache of references *to* this field. # The mechanism for getting at the related model is slightly odd - # ideally, we‘d just ask for field.related_model. However, related_model # is a cached property, and all the models haven‘t been loaded yet, so # we need to make sure we don‘t cache a string reference. if field.is_relation and hasattr(field.remote_field, ‘model‘) and field.remote_field.model: try: field.remote_field.model._meta._expire_cache(forward=False) except AttributeError: pass self._expire_cache() else: self._expire_cache(reverse=False) def setup_pk(self, field): if not self.pk and field.primary_key: self.pk = field field.serialize = False def setup_proxy(self, target): """ Do the internal setup so that the current model is a proxy for "target". """ self.pk = target._meta.pk self.proxy_for_model = target self.db_table = target._meta.db_table def __repr__(self): return ‘<Options for %s>‘ % self.object_name def __str__(self): return "%s.%s" % (self.app_label, self.model_name) def can_migrate(self, connection): """ Return True if the model can/should be migrated on the `connection`. `connection` can be either a real connection or a connection alias. """ if self.proxy or self.swapped or not self.managed: return False if isinstance(connection, str): connection = connections[connection] if self.required_db_vendor: return self.required_db_vendor == connection.vendor if self.required_db_features: return all(getattr(connection.features, feat, False) for feat in self.required_db_features) return True @property def verbose_name_raw(self): """Return the untranslated verbose name.""" with override(None): return str(self.verbose_name) @property def swapped(self): """ Has this model been swapped out for another? If so, return the model name of the replacement; otherwise, return None. For historical reasons, model name lookups using get_model() are case insensitive, so we make sure we are case insensitive here. """ if self.swappable: swapped_for = getattr(settings, self.swappable, None) if swapped_for: try: swapped_label, swapped_object = swapped_for.split(‘.‘) except ValueError: # setting not in the format app_label.model_name # raising ImproperlyConfigured here causes problems with # test cleanup code - instead it is raised in get_user_model # or as part of validation. return swapped_for if ‘%s.%s‘ % (swapped_label, swapped_object.lower()) != self.label_lower: return swapped_for return None @cached_property def managers(self): managers = [] seen_managers = set() bases = (b for b in self.model.mro() if hasattr(b, ‘_meta‘)) for depth, base in enumerate(bases): for manager in base._meta.local_managers: if manager.name in seen_managers: continue manager = copy.copy(manager) manager.model = self.model seen_managers.add(manager.name) managers.append((depth, manager.creation_counter, manager)) return make_immutable_fields_list( "managers", (m[2] for m in sorted(managers)), ) @cached_property def managers_map(self): return manager.name: manager for manager in self.managers @cached_property def base_manager(self): base_manager_name = self.base_manager_name if not base_manager_name: # Get the first parent‘s base_manager_name if there‘s one. for parent in self.model.mro()[1:]: if hasattr(parent, ‘_meta‘): if parent._base_manager.name != ‘_base_manager‘: base_manager_name = parent._base_manager.name break if base_manager_name: try: return self.managers_map[base_manager_name] except KeyError: raise ValueError( "%s has no manager named %r" % ( self.object_name, base_manager_name, ) ) manager = Manager() manager.name = ‘_base_manager‘ manager.model = self.model manager.auto_created = True return manager @cached_property def default_manager(self): default_manager_name = self.default_manager_name if not default_manager_name and not self.local_managers: # Get the first parent‘s default_manager_name if there‘s one. for parent in self.model.mro()[1:]: if hasattr(parent, ‘_meta‘): default_manager_name = parent._meta.default_manager_name break if default_manager_name: try: return self.managers_map[default_manager_name] except KeyError: raise ValueError( "%s has no manager named %r" % ( self.object_name, default_manager_name, ) ) if self.managers: return self.managers[0] @cached_property def fields(self): """ Return a list of all forward fields on the model and its parents, excluding ManyToManyFields. Private API intended only to be used by Django itself; get_fields() combined with filtering of field properties is the public API for obtaining this field list. """ # For legacy reasons, the fields property should only contain forward # fields that are not private or with a m2m cardinality. Therefore we # pass these three filters as filters to the generator. # The third lambda is a longwinded way of checking f.related_model - we don‘t # use that property directly because related_model is a cached property, # and all the models may not have been loaded yet; we don‘t want to cache # the string reference to the related_model. def is_not_an_m2m_field(f): return not (f.is_relation and f.many_to_many) def is_not_a_generic_relation(f): return not (f.is_relation and f.one_to_many) def is_not_a_generic_foreign_key(f): return not ( f.is_relation and f.many_to_one and not (hasattr(f.remote_field, ‘model‘) and f.remote_field.model) ) return make_immutable_fields_list( "fields", (f for f in self._get_fields(reverse=False) if is_not_an_m2m_field(f) and is_not_a_generic_relation(f) and is_not_a_generic_foreign_key(f)) ) @cached_property def concrete_fields(self): """ Return a list of all concrete fields on the model and its parents. Private API intended only to be used by Django itself; get_fields() combined with filtering of field properties is the public API for obtaining this field list. """ return make_immutable_fields_list( "concrete_fields", (f for f in self.fields if f.concrete) ) @cached_property def local_concrete_fields(self): """ Return a list of all concrete fields on the model. Private API intended only to be used by Django itself; get_fields() combined with filtering of field properties is the public API for obtaining this field list. """ return make_immutable_fields_list( "local_concrete_fields", (f for f in self.local_fields if f.concrete) ) @cached_property def many_to_many(self): """ Return a list of all many to many fields on the model and its parents. Private API intended only to be used by Django itself; get_fields() combined with filtering of field properties is the public API for obtaining this list. """ return make_immutable_fields_list( "many_to_many", (f for f in self._get_fields(reverse=False) if f.is_relation and f.many_to_many) ) @cached_property def related_objects(self): """ Return all related objects pointing to the current model. The related objects can come from a one-to-one, one-to-many, or many-to-many field relation type. Private API intended only to be used by Django itself; get_fields() combined with filtering of field properties is the public API for obtaining this field list. """ all_related_fields = self._get_fields(forward=False, reverse=True, include_hidden=True) return make_immutable_fields_list( "related_objects", (obj for obj in all_related_fields if not obj.hidden or obj.field.many_to_many) ) @cached_property def _forward_fields_map(self): res = fields = self._get_fields(reverse=False) for field in fields: res[field.name] = field # Due to the way Django‘s internals work, get_field() should also # be able to fetch a field by attname. In the case of a concrete # field with relation, includes the *_id name too try: res[field.attname] = field except AttributeError: pass return res @cached_property def fields_map(self): res = fields = self._get_fields(forward=False, include_hidden=True) for field in fields: res[field.name] = field # Due to the way Django‘s internals work, get_field() should also # be able to fetch a field by attname. In the case of a concrete # field with relation, includes the *_id name too try: res[field.attname] = field except AttributeError: pass return res def get_field(self, field_name): """ Return a field instance given the name of a forward or reverse field. """ try: # In order to avoid premature loading of the relation tree # (expensive) we prefer checking if the field is a forward field. return self._forward_fields_map[field_name] except KeyError: # If the app registry is not ready, reverse fields are # unavailable, therefore we throw a FieldDoesNotExist exception. if not self.apps.models_ready: raise FieldDoesNotExist( "%s has no field named ‘%s‘. The app cache isn‘t ready yet, " "so if this is an auto-created related field, it won‘t " "be available yet." % (self.object_name, field_name) ) try: # Retrieve field instance by name from cached or just-computed # field map. return self.fields_map[field_name] except KeyError: raise FieldDoesNotExist("%s has no field named ‘%s‘" % (self.object_name, field_name)) def get_base_chain(self, model): """ Return a list of parent classes leading to `model` (ordered from closest to most distant ancestor). This has to handle the case where `model` is a grandparent or even more distant relation. """ if not self.parents: return [] if model in self.parents: return [model] for parent in self.parents: res = parent._meta.get_base_chain(model) if res: res.insert(0, parent) return res return [] def get_parent_list(self): """ Return all the ancestors of this model as a list ordered by MRO. Useful for determining if something is an ancestor, regardless of lineage. """ result = OrderedSet(self.parents) for parent in self.parents: for ancestor in parent._meta.get_parent_list(): result.add(ancestor) return list(result) def get_ancestor_link(self, ancestor): """ Return the field on the current model which points to the given "ancestor". This is possible an indirect link (a pointer to a parent model, which points, eventually, to the ancestor). Used when constructing table joins for model inheritance. Return None if the model isn‘t an ancestor of this one. """ if ancestor in self.parents: return self.parents[ancestor] for parent in self.parents: # Tries to get a link field from the immediate parent parent_link = parent._meta.get_ancestor_link(ancestor) if parent_link: # In case of a proxied model, the first link # of the chain to the ancestor is that parent # links return self.parents[parent] or parent_link def get_path_to_parent(self, parent): """ Return a list of PathInfos containing the path from the current model to the parent model, or an empty list if parent is not a parent of the current model. """ if self.model is parent: return [] # Skip the chain of proxy to the concrete proxied model. proxied_model = self.concrete_model path = [] opts = self for int_model in self.get_base_chain(parent): if int_model is proxied_model: opts = int_model._meta else: final_field = opts.parents[int_model] targets = (final_field.remote_field.get_related_field(),) opts = int_model._meta path.append(PathInfo( from_opts=final_field.model._meta, to_opts=opts, target_fields=targets, join_field=final_field, m2m=False, direct=True, filtered_relation=None, )) return path def get_path_from_parent(self, parent): """ Return a list of PathInfos containing the path from the parent model to the current model, or an empty list if parent is not a parent of the current model. """ if self.model is parent: return [] model = self.concrete_model # Get a reversed base chain including both the current and parent # models. chain = model._meta.get_base_chain(parent) chain.reverse() chain.append(model) # Construct a list of the PathInfos between models in chain. path = [] for i, ancestor in enumerate(chain[:-1]): child = chain[i + 1] link = child._meta.get_ancestor_link(ancestor) path.extend(link.get_reverse_path_info()) return path def _populate_directed_relation_graph(self): """ This method is used by each model to find its reverse objects. As this method is very expensive and is accessed frequently (it looks up every field in a model, in every app), it is computed on first access and then is set as a property on every model. """ related_objects_graph = defaultdict(list) all_models = self.apps.get_models(include_auto_created=True) for model in all_models: opts = model._meta # Abstract model‘s fields are copied to child models, hence we will # see the fields from the child models. if opts.abstract: continue fields_with_relations = ( f for f in opts._get_fields(reverse=False, include_parents=False) if f.is_relation and f.related_model is not None ) for f in fields_with_relations: if not isinstance(f.remote_field.model, str): related_objects_graph[f.remote_field.model._meta.concrete_model._meta].append(f) for model in all_models: # Set the relation_tree using the internal __dict__. In this way # we avoid calling the cached property. In attribute lookup, # __dict__ takes precedence over a data descriptor (such as # @cached_property). This means that the _meta._relation_tree is # only called if related_objects is not in __dict__. related_objects = related_objects_graph[model._meta.concrete_model._meta] model._meta.__dict__[‘_relation_tree‘] = related_objects # It seems it is possible that self is not in all_models, so guard # against that with default for get(). return self.__dict__.get(‘_relation_tree‘, EMPTY_RELATION_TREE) @cached_property def _relation_tree(self): return self._populate_directed_relation_graph() def _expire_cache(self, forward=True, reverse=True): # This method is usually called by apps.cache_clear(), when the # registry is finalized, or when a new field is added. if forward: for cache_key in self.FORWARD_PROPERTIES: if cache_key in self.__dict__: delattr(self, cache_key) if reverse and not self.abstract: for cache_key in self.REVERSE_PROPERTIES: if cache_key in self.__dict__: delattr(self, cache_key) self._get_fields_cache = def get_fields(self, include_parents=True, include_hidden=False): """ Return a list of fields associated to the model. By default, include forward and reverse fields, fields derived from inheritance, but not hidden fields. The returned fields can be changed using the parameters: - include_parents: include fields derived from inheritance - include_hidden: include fields that have a related_name that starts with a "+" """ if include_parents is False: include_parents = PROXY_PARENTS return self._get_fields(include_parents=include_parents, include_hidden=include_hidden) def _get_fields(self, forward=True, reverse=True, include_parents=True, include_hidden=False, seen_models=None): """ Internal helper function to return fields of the model. * If forward=True, then fields defined on this model are returned. * If reverse=True, then relations pointing to this model are returned. * If include_hidden=True, then fields with is_hidden=True are returned. * The include_parents argument toggles if fields from parent models should be included. It has three values: True, False, and PROXY_PARENTS. When set to PROXY_PARENTS, the call will return all fields defined for the current model or any of its parents in the parent chain to the model‘s concrete model. """ if include_parents not in (True, False, PROXY_PARENTS): raise TypeError("Invalid argument for include_parents: %s" % (include_parents,)) # This helper function is used to allow recursion in ``get_fields()`` # implementation and to provide a fast way for Django‘s internals to # access specific subsets of fields. # We must keep track of which models we have already seen. Otherwise we # could include the same field multiple times from different models. topmost_call = False if seen_models is None: seen_models = set() topmost_call = True seen_models.add(self.model) # Creates a cache key composed of all arguments cache_key = (forward, reverse, include_parents, include_hidden, topmost_call) try: # In order to avoid list manipulation. Always return a shallow copy # of the results. return self._get_fields_cache[cache_key] except KeyError: pass fields = [] # Recursively call _get_fields() on each parent, with the same # options provided in this call. if include_parents is not False: for parent in self.parents: # In diamond inheritance it is possible that we see the same # model from two different routes. In that case, avoid adding # fields from the same parent again. if parent in seen_models: continue if (parent._meta.concrete_model != self.concrete_model and include_parents == PROXY_PARENTS): continue for obj in parent._meta._get_fields( forward=forward, reverse=reverse, include_parents=include_parents, include_hidden=include_hidden, seen_models=seen_models): if getattr(obj, ‘parent_link‘, False) and obj.model != self.concrete_model: continue fields.append(obj) if reverse and not self.proxy: # Tree is computed once and cached until the app cache is expired. # It is composed of a list of fields pointing to the current model # from other models. all_fields = self._relation_tree for field in all_fields: # If hidden fields should be included or the relation is not # intentionally hidden, add to the fields dict. if include_hidden or not field.remote_field.hidden: fields.append(field.remote_field) if forward: fields += self.local_fields fields += self.local_many_to_many # Private fields are recopied to each child model, and they get a # different model as field.model in each child. Hence we have to # add the private fields separately from the topmost call. If we # did this recursively similar to local_fields, we would get field # instances with field.model != self.model. if topmost_call: fields += self.private_fields # In order to avoid list manipulation. Always # return a shallow copy of the results fields = make_immutable_fields_list("get_fields()", fields) # Store result into cache for later access self._get_fields_cache[cache_key] = fields return fields @property def has_auto_field(self): warnings.warn( ‘Model._meta.has_auto_field is deprecated in favor of checking if ‘ ‘Model._meta.auto_field is not None.‘, RemovedInDjango21Warning, stacklevel=2 ) return self.auto_field is not None @has_auto_field.setter def has_auto_field(self, value): pass @cached_property def _property_names(self): """Return a set of the names of the properties defined on the model.""" names = [] for name in dir(self.model): attr = inspect.getattr_static(self.model, name) if isinstance(attr, property): names.append(name) return frozenset(names)
- fields
In [26]: models.Book._meta.fields Out[26]: (<django.db.models.fields.AutoField: id>, <django.db.models.fields.CharField: title>, <django.db.models.fields.IntegerField: price>, <django.db.models.fields.DateField: pub_date>, <django.db.models.fields.related.ForeignKey: publish>)
- app_label
In [24]: models.Book._meta.app_label Out[24]: ‘app01‘
- model_name
In [21]: models.Book._meta.model_name Out[21]: ‘book‘
- db_table
In [39]: models.Book._meta.db_table Out[39]: ‘app01_book‘
- verbose_name
In [22]: models.Book._meta.verbose_name Out[22]: ‘book‘
- local_many_to_many
In [23]: models.Book._meta.local_many_to_many
Out[23]: [<django.db.models.fields.related.ManyToManyField: authors>]
- _relation_tree
In [60]: models.Book._meta._relation_tree
Out[60]: [<django.db.models.fields.related.ForeignKey: book>]
- model
In [59]: models.Book._meta.model
Out[59]: app01.models.Book
可以直接使用: models.Book._meta.model.objects.all()
上述就是通过表名._meta,然后再调用相应的属性、方法,取得每一个model字段对象以及app_label,model_name等。
(二)model字段对象的使用
model字段位于django.db.models.field,其中Field为所有字段的基类:
@total_ordering class Field(RegisterLookupMixin): """Base class for all field types""" # Designates whether empty strings fundamentally are allowed at the # database level. empty_strings_allowed = True empty_values = list(validators.EMPTY_VALUES) # These track each time a Field instance is created. Used to retain order. # The auto_creation_counter is used for fields that Django implicitly # creates, creation_counter is used for all user-specified fields. creation_counter = 0 auto_creation_counter = -1 default_validators = [] # Default set of validators default_error_messages = ‘invalid_choice‘: _(‘Value %(value)r is not a valid choice.‘), ‘null‘: _(‘This field cannot be null.‘), ‘blank‘: _(‘This field cannot be blank.‘), ‘unique‘: _(‘%(model_name)s with this %(field_label)s ‘ ‘already exists.‘), # Translators: The ‘lookup_type‘ is one of ‘date‘, ‘year‘ or ‘month‘. # Eg: "Title must be unique for pub_date year" ‘unique_for_date‘: _("%(field_label)s must be unique for " "%(date_field_label)s %(lookup_type)s."), system_check_deprecated_details = None system_check_removed_details = None # Field flags hidden = False many_to_many = None many_to_one = None one_to_many = None one_to_one = None related_model = None # Generic field type description, usually overridden by subclasses def _description(self): return _(‘Field of type: %(field_type)s‘) % ‘field_type‘: self.__class__.__name__ description = property(_description) def __init__(self, verbose_name=None, name=None, primary_key=False, max_length=None, unique=False, blank=False, null=False, db_index=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, unique_for_date=None, unique_for_month=None, unique_for_year=None, choices=None, help_text=‘‘, db_column=None, db_tablespace=None, auto_created=False, validators=(), error_messages=None): self.name = name self.verbose_name = verbose_name # May be set by set_attributes_from_name self._verbose_name = verbose_name # Store original for deconstruction self.primary_key = primary_key self.max_length, self._unique = max_length, unique self.blank, self.null = blank, null self.remote_field = rel self.is_relation = self.remote_field is not None self.default = default self.editable = editable self.serialize = serialize self.unique_for_date = unique_for_date self.unique_for_month = unique_for_month self.unique_for_year = unique_for_year if isinstance(choices, collections.Iterator): choices = list(choices) self.choices = choices or [] self.help_text = help_text self.db_index = db_index self.db_column = db_column self._db_tablespace = db_tablespace self.auto_created = auto_created # Adjust the appropriate creation counter, and save our local copy. if auto_created: self.creation_counter = Field.auto_creation_counter Field.auto_creation_counter -= 1 else: self.creation_counter = Field.creation_counter Field.creation_counter += 1 self._validators = list(validators) # Store for deconstruction later messages = for c in reversed(self.__class__.__mro__): messages.update(getattr(c, ‘default_error_messages‘, )) messages.update(error_messages or ) self._error_messages = error_messages # Store for deconstruction later self.error_messages = messages def __str__(self): """ Return "app_label.model_label.field_name" for fields attached to models. """ if not hasattr(self, ‘model‘): return super().__str__() model = self.model app = model._meta.app_label return ‘%s.%s.%s‘ % (app, model._meta.object_name, self.name) def __repr__(self): """Display the module, class, and name of the field.""" path = ‘%s.%s‘ % (self.__class__.__module__, self.__class__.__qualname__) name = getattr(self, ‘name‘, None) if name is not None: return ‘<%s: %s>‘ % (path, name) return ‘<%s>‘ % path def check(self, **kwargs): errors = [] errors.extend(self._check_field_name()) errors.extend(self._check_choices()) errors.extend(self._check_db_index()) errors.extend(self._check_null_allowed_for_primary_keys()) errors.extend(self._check_backend_specific_checks(**kwargs)) errors.extend(self._check_validators()) errors.extend(self._check_deprecation_details()) return errors def _check_field_name(self): """ Check if field name is valid, i.e. 1) does not end with an underscore, 2) does not contain "__" and 3) is not "pk". """ if self.name.endswith(‘_‘): return [ checks.Error( ‘Field names must not end with an underscore.‘, obj=self, id=‘fields.E001‘, ) ] elif LOOKUP_SEP in self.name: return [ checks.Error( ‘Field names must not contain "%s".‘ % (LOOKUP_SEP,), obj=self, id=‘fields.E002‘, ) ] elif self.name == ‘pk‘: return [ checks.Error( "‘pk‘ is a reserved word that cannot be used as a field name.", obj=self, id=‘fields.E003‘, ) ] else: return [] def _check_choices(self): if self.choices: if isinstance(self.choices, str) or not is_iterable(self.choices): return [ checks.Error( "‘choices‘ must be an iterable (e.g., a list or tuple).", obj=self, id=‘fields.E004‘, ) ] elif any(isinstance(choice, str) or not is_iterable(choice) or len(choice) != 2 for choice in self.choices): return [ checks.Error( "‘choices‘ must be an iterable containing " "(actual value, human readable name) tuples.", obj=self, id=‘fields.E005‘, ) ] else: return [] else: return [] def _check_db_index(self): if self.db_index not in (None, True, False): return [ checks.Error( "‘db_index‘ must be None, True or False.", obj=self, id=‘fields.E006‘, ) ] else: return [] def _check_null_allowed_for_primary_keys(self): if (self.primary_key and self.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). return [ checks.Error( ‘Primary keys must not have null=True.‘, hint=(‘Set null=False on the field, or ‘ ‘remove primary_key=True argument.‘), obj=self, id=‘fields.E007‘, ) ] else: return [] def _check_backend_specific_checks(self, **kwargs): app_label = self.model._meta.app_label for db in connections: if router.allow_migrate(db, app_label, model_name=self.model._meta.model_name): return connections[db].validation.check_field(self, **kwargs) return [] def _check_validators(self): errors = [] for i, validator in enumerate(self.validators): if not callable(validator): errors.append( checks.Error( "All ‘validators‘ must be callable.", hint=( "validators[i] (repr) isn‘t a function or " "instance of a validator class.".format( i=i, repr=repr(validator), ) ), obj=self, id=‘fields.E008‘, ) ) return errors def _check_deprecation_details(self): if self.system_check_removed_details is not None: return [ checks.Error( self.system_check_removed_details.get( ‘msg‘, ‘%s has been removed except for support in historical ‘ ‘migrations.‘ % self.__class__.__name__ ), hint=self.system_check_removed_details.get(‘hint‘), obj=self, id=self.system_check_removed_details.get(‘id‘, ‘fields.EXXX‘), ) ] elif self.system_check_deprecated_details is not None: return [ checks.Warning( self.system_check_deprecated_details.get( ‘msg‘, ‘%s has been deprecated.‘ % self.__class__.__name__ ), hint=self.system_check_deprecated_details.get(‘hint‘), obj=self, id=self.system_check_deprecated_details.get(‘id‘, ‘fields.WXXX‘), ) ] return [] def get_col(self, alias, output_field=None): if output_field is None: output_field = self if alias != self.model._meta.db_table or output_field != self: from django.db.models.expressions import Col return Col(alias, self, output_field) else: return self.cached_col @cached_property def cached_col(self): from django.db.models.expressions import Col return Col(self.model._meta.db_table, self) def select_format(self, compiler, sql, params): """ Custom format for select clauses. For example, GIS columns need to be selected as AsText(table.col) on mysql as the table.col data can‘t be used by Django. """ return sql, params def deconstruct(self): """ Return enough information to recreate the field as a 4-tuple: * The name of the field on the model, if contribute_to_class() has been run. * The import path of the field, including the class:e.g. django.db.models.IntegerField This should be the most portable version, so less specific may be better. * A list of positional arguments. * A dict of keyword arguments. Note that the positional or keyword arguments must contain values of the following types (including inner values of collection types): * None, bool, str, int, float, complex, set, frozenset, list, tuple, dict * UUID * datetime.datetime (naive), datetime.date * top-level classes, top-level functions - will be referenced by their full import path * Storage instances - these have their own deconstruct() method This is because the values here must be serialized into a text format (possibly new Python code, possibly JSON) and these are the only types with encoding handlers defined. There‘s no need to return the exact way the field was instantiated this time, just ensure that the resulting field is the same - prefer keyword arguments over positional ones, and omit parameters with their default values. """ # Short-form way of fetching all the default parameters keywords = possibles = "verbose_name": None, "primary_key": False, "max_length": None, "unique": False, "blank": False, "null": False, "db_index": False, "default": NOT_PROVIDED, "editable": True, "serialize": True, "unique_for_date": None, "unique_for_month": None, "unique_for_year": None, "choices": [], "help_text": ‘‘, "db_column": None, "db_tablespace": None, "auto_created": False, "validators": [], "error_messages": None, attr_overrides = "unique": "_unique", "error_messages": "_error_messages", "validators": "_validators", "verbose_name": "_verbose_name", "db_tablespace": "_db_tablespace", equals_comparison = "choices", "validators" for name, default in possibles.items(): value = getattr(self, attr_overrides.get(name, name)) # Unroll anything iterable for choices into a concrete list if name == "choices" and isinstance(value, collections.Iterable): value = list(value) # Do correct kind of comparison if name in equals_comparison: if value != default: keywords[name] = value else: if value is not default: keywords[name] = value # Work out path - we shorten it for known Django core fields path = "%s.%s" % (self.__class__.__module__, self.__class__.__qualname__) if path.startswith("django.db.models.fields.related"): path = path.replace("django.db.models.fields.related", "django.db.models") if path.startswith("django.db.models.fields.files"): path = path.replace("django.db.models.fields.files", "django.db.models") if path.startswith("django.db.models.fields.proxy"): path = path.replace("django.db.models.fields.proxy", "django.db.models") if path.startswith("django.db.models.fields"): path = path.replace("django.db.models.fields", "django.db.models") # Return basic info - other fields should override this. return (self.name, path, [], keywords) def clone(self): """ Uses deconstruct() to clone a new copy of this Field. Will not preserve any class attachments/attribute names. """ name, path, args, kwargs = self.deconstruct() return self.__class__(*args, **kwargs) def __eq__(self, other): # Needed for @total_ordering if isinstance(other, Field): return self.creation_counter == other.creation_counter return NotImplemented def __lt__(self, other): # This is needed because bisect does not take a comparison function. if isinstance(other, Field): return self.creation_counter < other.creation_counter return NotImplemented def __hash__(self): return hash(self.creation_counter) def __deepcopy__(self, memodict): # We don‘t have to deepcopy very much here, since most things are not # intended to be altered after initial creation. obj = copy.copy(self) if self.remote_field: obj.remote_field = copy.copy(self.remote_field) if hasattr(self.remote_field, ‘field‘) and self.remote_field.field is self: obj.remote_field.field = obj memodict[id(self)] = obj return obj def __copy__(self): # We need to avoid hitting __reduce__, so define this # slightly weird copy construct. obj = Empty() obj.__class__ = self.__class__ obj.__dict__ = self.__dict__.copy() return obj def __reduce__(self): """ Pickling should return the model._meta.fields instance of the field, not a new copy of that field. So, use the app registry to load the model and then the field back. """ if not hasattr(self, ‘model‘): # Fields are sometimes used without attaching them to models (for # example in aggregation). In this case give back a plain field # instance. The code below will create a new empty instance of # class self.__class__, then update its dict with self.__dict__ # values - so, this is very close to normal pickle. state = self.__dict__.copy() # The _get_default cached_property can‘t be pickled due to lambda # usage. state.pop(‘_get_default‘, None) return _empty, (self.__class__,), state return _load_field, (self.model._meta.app_label, self.model._meta.object_name, self.name) def get_pk_value_on_save(self, instance): """ Hook to generate new PK values on save. This method is called when saving instances with no primary key value set. If this method returns something else than None, then the returned value is used when saving the new instance. """ if self.default: return self.get_default() return None def to_python(self, value): """ Convert the input value into the expected Python data type, raising django.core.exceptions.ValidationError if the data can‘t be converted. Return the converted value. Subclasses should override this. """ return value @cached_property def validators(self): """ Some validators can‘t be created at field initialization time. This method provides a way to delay their creation until required. """ return list(itertools.chain(self.default_validators, self._validators)) def run_validators(self, value): if value in self.empty_values: return errors = [] for v in self.validators: try: v(value) except exceptions.ValidationError as e: if hasattr(e, ‘code‘) and e.code in self.error_messages: e.message = self.error_messages[e.code] errors.extend(e.error_list) if errors: raise exceptions.ValidationError(errors) def validate(self, value, model_instance): """ Validate value and raise ValidationError if necessary. Subclasses should override this to provide validation logic. """ if not self.editable: # Skip validation for non-editable fields. return if self.choices and value not in self.empty_values: for option_key, option_value in self.choices: if isinstance(option_value, (list, tuple)): # This is an optgroup, so look inside the group for # options. for optgroup_key, optgroup_value in option_value: if value == optgroup_key: return elif value == option_key: return raise exceptions.ValidationError( self.error_messages[‘invalid_choice‘], code=‘invalid_choice‘, params=‘value‘: value, ) if value is None and not self.null: raise exceptions.ValidationError(self.error_messages[‘null‘], code=‘null‘) if not self.blank and value in self.empty_values: raise exceptions.ValidationError(self.error_messages[‘blank‘], code=‘blank‘) def clean(self, value, model_instance): """ Convert the value‘s type and run validation. Validation errors from to_python() and validate() are propagated. Return the correct value if no error is raised. """ value = self.to_python(value) self.validate(value, model_instance) self.run_validators(value) return value def db_type_parameters(self, connection): return DictWrapper(self.__dict__, connection.ops.quote_name, ‘qn_‘) def db_check(self, connection): """ Return the database column check constraint for this field, for the provided connection. Works the same way as db_type() for the case that get_internal_type() does not map to a preexisting model field. """ data = self.db_type_parameters(connection) try: return connection.data_type_check_constraints[self.get_internal_type()] % data except KeyError: return None def db_type(self, connection): """ Return the database column data type for this field, for the provided connection. """ # The default implementation of this method looks at the # backend-specific data_types dictionary, looking up the field by its # "internal type". # # A Field class can implement the get_internal_type() method to specify # which *preexisting* Django Field class it‘s most similar to -- i.e., # a custom field might be represented by a TEXT column type, which is # the same as the TextField Django field type, which means the custom # field‘s get_internal_type() returns ‘TextField‘. # # But the limitation of the get_internal_type() / data_types approach # is that it cannot handle database column types that aren‘t already # mapped to one of the built-in Django field types. In this case, you # can implement db_type() instead of get_internal_type() to specify # exactly which wacky database column type you want to use. data = self.db_type_parameters(connection) try: return connection.data_types[self.get_internal_type()] % data except KeyError: return None def rel_db_type(self, connection): """ Return the data type that a related field pointing to this field should use. For example, this method is called by ForeignKey and OneToOneField to determine its data type. """ return self.db_type(connection) def cast_db_type(self, connection): """Return the data type to use in the Cast() function.""" db_type = connection.ops.cast_data_types.get(self.get_internal_type()) if db_type: return db_type % self.db_type_parameters(connection) return self.db_type(connection) def db_parameters(self, connection): """ Extension of db_type(), providing a range of different return values (type, checks). This will look at db_type(), allowing custom model fields to override it. """ type_string = self.db_type(connection) check_string = self.db_check(connection) return "type": type_string, "check": check_string, def db_type_suffix(self, connection): return connection.data_types_suffix.get(self.get_internal_type()) def get_db_converters(self, connection): if hasattr(self, ‘from_db_value‘): return [self.from_db_value] return [] @property def unique(self): return self._unique or self.primary_key @property def db_tablespace(self): return self._db_tablespace or settings.DEFAULT_INDEX_TABLESPACE def set_attributes_from_name(self, name): if not self.name: self.name = name self.attname, self.column = self.get_attname_column() self.concrete = self.column is not None if self.verbose_name is None and self.name: self.verbose_name = self.name.replace(‘_‘, ‘ ‘) def contribute_to_class(self, cls, name, private_only=False): """ Register the field with the model class it belongs to. If private_only is True, create a separate instance of this field for every subclass of cls, even if cls is not an abstract model. """ self.set_attributes_from_name(name) self.model = cls if private_only: cls._meta.add_field(self, private=True) else: cls._meta.add_field(self) if self.column: # Don‘t override classmethods with the descriptor. This means that # if you have a classmethod and a field with the same name, then # such fields can‘t be deferred (we don‘t have a check for this). if not getattr(cls, self.attname, None): setattr(cls, self.attname, DeferredAttribute(self.attname, cls)) if self.choices: setattr(cls, ‘get_%s_display‘ % self.name, partialmethod(cls._get_FIELD_display, field=self)) def get_filter_kwargs_for_object(self, obj): """ Return a dict that when passed as kwargs to self.model.filter(), would yield all instances having the same value for this field as obj has. """ return self.name: getattr(obj, self.attname) def get_attname(self): return self.name def get_attname_column(self): attname = self.get_attname() column = self.db_column or attname return attname, column def get_internal_type(self): return self.__class__.__name__ def pre_save(self, model_instance, add): """Return field‘s value just before saving.""" return getattr(model_instance, self.attname) def get_prep_value(self, value): """Perform preliminary non-db specific value checks and conversions.""" if isinstance(value, Promise): value = value._proxy____cast() return value def get_db_prep_value(self, value, connection, prepared=False): """ Return field‘s value prepared for interacting with the database backend. Used by the default implementations of get_db_prep_save(). """ if not prepared: value = self.get_prep_value(value) return value def get_db_prep_save(self, value, connection): """Return field‘s value prepared for saving into a database.""" return self.get_db_prep_value(value, connection=connection, prepared=False) def has_default(self): """Return a boolean of whether this field has a default value.""" return self.default is not NOT_PROVIDED def get_default(self): """Return the default value for this field.""" return self._get_default() @cached_property def _get_default(self): if self.has_default(): if callable(self.default): return self.default return lambda: self.default if not self.empty_strings_allowed or self.null and not connection.features.interprets_empty_strings_as_nulls: return return_None return str # return empty string def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_choices_to=None): """ Return choices with a default blank choices included, for use as <select> choices for this field. """ blank_defined = False choices = list(self.choices) if self.choices else [] named_groups = choices and isinstance(choices[0][1], (list, tuple)) if not named_groups: for choice, __ in choices: if choice in (‘‘, None): blank_defined = True break first_choice = (blank_choice if include_blank and not blank_defined else []) if self.choices: return first_choice + choices rel_model = self.remote_field.model limit_choices_to = limit_choices_to or self.get_limit_choices_to() if hasattr(self.remote_field, ‘get_related_field‘): lst = [(getattr(x, self.remote_field.get_related_field().attname), smart_text(x)) for x in rel_model._default_manager.complex_filter( limit_choices_to)] else: lst = [(x.pk, smart_text(x)) for x in rel_model._default_manager.complex_filter( limit_choices_to)] return first_choice + lst def value_to_string(self, obj): """ Return a string value of this field from the passed obj. This is used by the serialization framework. """ return str(self.value_from_object(obj)) def _get_flatchoices(self): """Flattened version of choices tuple.""" flat = [] for choice, value in self.choices: if isinstance(value, (list, tuple)): flat.extend(value) else: flat.append((choice, value)) return flat flatchoices = property(_get_flatchoices) def save_form_data(self, instance, data): setattr(instance, self.name, data) def formfield(self, form_class=None, choices_form_class=None, **kwargs): """Return a django.forms.Field instance for this field.""" defaults = ‘required‘: not self.blank, ‘label‘: capfirst(self.verbose_name), ‘help_text‘: self.help_text if self.has_default(): if callable(self.default): defaults[‘initial‘] = self.default defaults[‘show_hidden_initial‘] = True else: defaults[‘initial‘] = self.get_default() if self.choices: # Fields with choices get special treatment. include_blank = (self.blank or not (self.has_default() or ‘initial‘ in kwargs)) defaults[‘choices‘] = self.get_choices(include_blank=include_blank) defaults[‘coerce‘] = self.to_python if self.null: defaults[‘empty_value‘] = None if choices_form_class is not None: form_class = choices_form_class else: form_class = forms.TypedChoiceField # Many of the subclass-specific formfield arguments (min_value, # max_value) don‘t apply for choice fields, so be sure to only pass # the values that TypedChoiceField will understand. for k in list(kwargs): if k not in (‘coerce‘, ‘empty_value‘, ‘choices‘, ‘required‘, ‘widget‘, ‘label‘, ‘initial‘, ‘help_text‘, ‘error_messages‘, ‘show_hidden_initial‘, ‘disabled‘): del kwargs[k] defaults.update(kwargs) if form_class is None: form_class = forms.CharField return form_class(**defaults) def value_from_object(self, obj): """Return the value of this field in the given model instance.""" return getattr(obj, self.attname)
1、获取字段对象
上述已经通过_meta这个API获取字段的对象:
In [61]: models.Book._meta.get_field("title") Out[61]: <django.db.models.fields.CharField: title>
2、字段对象的属性和方法
- 所有字段都存在的
In [62]: f1 = models.Book._meta.get_field("title") In [63]: f1.name Out[63]: ‘title‘ In [64]: f1.verbose_name Out[64]: ‘title‘
更多参考下面的源码:
class Field(RegisterLookupMixin): """Base class for all field types""" # Designates whether empty strings fundamentally are allowed at the # database level. empty_strings_allowed = True empty_values = list(validators.EMPTY_VALUES) # These track each time a Field instance is created. Used to retain order. # The auto_creation_counter is used for fields that Django implicitly # creates, creation_counter is used for all user-specified fields. creation_counter = 0 auto_creation_counter = -1 default_validators = [] # Default set of validators default_error_messages = ‘invalid_choice‘: _(‘Value %(value)r is not a valid choice.‘), ‘null‘: _(‘This field cannot be null.‘), ‘blank‘: _(‘This field cannot be blank.‘), ‘unique‘: _(‘%(model_name)s with this %(field_label)s ‘ ‘already exists.‘), # Translators: The ‘lookup_type‘ is one of ‘date‘, ‘year‘ or ‘month‘. # Eg: "Title must be unique for pub_date year" ‘unique_for_date‘: _("%(field_label)s must be unique for " "%(date_field_label)s %(lookup_type)s."), system_check_deprecated_details = None system_check_removed_details = None # Field flags hidden = False many_to_many = None many_to_one = None one_to_many = None one_to_one = None related_model = None # Generic field type description, usually overridden by subclasses def _description(self): return _(‘Field of type: %(field_type)s‘) % ‘field_type‘: self.__class__.__name__ description = property(_description) def __init__(self, verbose_name=None, name=None, primary_key=False, max_length=None, unique=False, blank=False, null=False, db_index=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, unique_for_date=None, unique_for_month=None, unique_for_year=None, choices=None, help_text=‘‘, db_column=None, db_tablespace=None, auto_created=False, validators=(), error_messages=None): self.name = name self.verbose_name = verbose_name # May be set by set_attributes_from_name self._verbose_name = verbose_name # Store original for deconstruction self.primary_key = primary_key self.max_length, self._unique = max_length, unique self.blank, self.null = blank, null self.remote_field = rel self.is_relation = self.remote_field is not None self.default = default self.editable = editable self.serialize = serialize self.unique_for_date = unique_for_date self.unique_for_month = unique_for_month self.unique_for_year = unique_for_year if isinstance(choices, collections.Iterator): choices = list(choices) self.choices = choices or [] self.help_text = help_text self.db_index = db_index self.db_column = db_column self._db_tablespace = db_tablespace self.auto_created = auto_created # Adjust the appropriate creation counter, and save our local copy. if auto_created: self.creation_counter = Field.auto_creation_counter Field.auto_creation_counter -= 1 else: self.creation_counter = Field.creation_counter Field.creation_counter += 1 self._validators = list(validators) # Store for deconstruction later messages = for c in reversed(self.__class__.__mro__): messages.update(getattr(c, ‘default_error_messages‘, )) messages.update(error_messages or ) self._error_messages = error_messages # Store for deconstruction later self.error_messages = messages def __str__(self): """ Return "app_label.model_label.field_name" for fields attached to models. """ if not hasattr(self, ‘model‘): return super().__str__() model = self.model app = model._meta.app_label return ‘%s.%s.%s‘ % (app, model._meta.object_name, self.name) def __repr__(self): """Display the module, class, and name of the field.""" path = ‘%s.%s‘ % (self.__class__.__module__, self.__class__.__qualname__) name = getattr(self, ‘name‘, None) if name is not None: return ‘<%s: %s>‘ % (path, name) return ‘<%s>‘ % path def check(self, **kwargs): errors = [] errors.extend(self._check_field_name()) errors.extend(self._check_choices()) errors.extend(self._check_db_index()) errors.extend(self._check_null_allowed_for_primary_keys()) errors.extend(self._check_backend_specific_checks(**kwargs)) errors.extend(self._check_validators()) errors.extend(self._check_deprecation_details()) return errors def _check_field_name(self): """ Check if field name is valid, i.e. 1) does not end with an underscore, 2) does not contain "__" and 3) is not "pk". """ if self.name.endswith(‘_‘): return [ checks.Error( ‘Field names must not end with an underscore.‘, obj=self, id=‘fields.E001‘, ) ] elif LOOKUP_SEP in self.name: return [ checks.Error( ‘Field names must not contain "%s".‘ % (LOOKUP_SEP,), obj=self, id=‘fields.E002‘, ) ] elif self.name == ‘pk‘: return [ checks.Error( "‘pk‘ is a reserved word that cannot be used as a field name.", obj=self, id=‘fields.E003‘, ) ] else: return [] def _check_choices(self): if self.choices: if isinstance(self.choices, str) or not is_iterable(self.choices): return [ checks.Error( "‘choices‘ must be an iterable (e.g., a list or tuple).", obj=self, id=‘fields.E004‘, ) ] elif any(isinstance(choice, str) or not is_iterable(choice) or len(choice) != 2 for choice in self.choices): return [ checks.Error( "‘choices‘ must be an iterable containing " "(actual value, human readable name) tuples.", obj=self, id=‘fields.E005‘, ) ] else: return [] else: return [] def _check_db_index(self): if self.db_index not in (None, True, False): return [ checks.Error( "‘db_index‘ must be None, True or False.", obj=self, id=‘fields.E006‘, ) ] else: return [] def _check_null_allowed_for_primary_keys(self): if (self.primary_key and self.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). return [ checks.Error( ‘Primary keys must not have null=True.‘, hint=(‘Set null=False on the field, or ‘ ‘remove primary_key=True argument.‘), obj=self, id=‘fields.E007‘, ) ] else: return [] def _check_backend_specific_checks(self, **kwargs): app_label = self.model._meta.app_label for db in connections: if router.allow_migrate(db, app_label, model_name=self.model._meta.model_name): return connections[db].validation.check_field(self, **kwargs) return [] def _check_validators(self): errors = [] for i, validator in enumerate(self.validators): if not callable(validator): errors.append( checks.Error( "All ‘validators‘ must be callable.", hint=( "validators[i] (repr) isn‘t a function or " "instance of a validator class.".format( i=i, repr=repr(validator), ) ), obj=self, id=‘fields.E008‘, ) ) return errors def _check_deprecation_details(self): if self.system_check_removed_details is not None: return [ checks.Error( self.system_check_removed_details.get( ‘msg‘, ‘%s has been removed except for support in historical ‘ ‘migrations.‘ % self.__class__.__name__ ), hint=self.system_check_removed_details.get(‘hint‘), obj=self, id=self.system_check_removed_details.get(‘id‘, ‘fields.EXXX‘), ) ] elif self.system_check_deprecated_details is not None: return [ checks.Warning( self.system_check_deprecated_details.get( ‘msg‘, ‘%s has been deprecated.‘ % self.__class__.__name__ ), hint=self.system_check_deprecated_details.get(‘hint‘), obj=self, id=self.system_check_deprecated_details.get(‘id‘, ‘fields.WXXX‘), ) ] return [] def get_col(self, alias, output_field=None): if output_field is None: output_field = self if alias != self.model._meta.db_table or output_field != self: from django.db.models.expressions import Col return Col(alias, self, output_field) else: return self.cached_col @cached_property def cached_col(self): from django.db.models.expressions import Col return Col(self.model._meta.db_table, self) def select_format(self, compiler, sql, params): """ Custom format for select clauses. For example, GIS columns need to be selected as AsText(table.col) on MySQL as the table.col data can‘t be used by Django. """ return sql, params def deconstruct(self): """ Return enough information to recreate the field as a 4-tuple: * The name of the field on the model, if contribute_to_class() has been run. * The import path of the field, including the class:e.g. django.db.models.IntegerField This should be the most portable version, so less specific may be better. * A list of positional arguments. * A dict of keyword arguments. Note that the positional or keyword arguments must contain values of the following types (including inner values of collection types): * None, bool, str, int, float, complex, set, frozenset, list, tuple, dict * UUID * datetime.datetime (naive), datetime.date * top-level classes, top-level functions - will be referenced by their full import path * Storage instances - these have their own deconstruct() method This is because the values here must be serialized into a text format (possibly new Python code, possibly JSON) and these are the only types with encoding handlers defined. There‘s no need to return the exact way the field was instantiated this time, just ensure that the resulting field is the same - prefer keyword arguments over positional ones, and omit parameters with their default values. """ # Short-form way of fetching all the default parameters keywords = possibles = "verbose_name": None, "primary_key": False, "max_length": None, "unique": False, "blank": False, "null": False, "db_index": False, "default": NOT_PROVIDED, "editable": True, "serialize": True, "unique_for_date": None, "unique_for_month": None, "unique_for_year": None, "choices": [], "help_text": ‘‘, "db_column": None, "db_tablespace": None, "auto_created": False, "validators": [], "error_messages": None, attr_overrides = "unique": "_unique", "error_messages": "_error_messages", "validators": "_validators", "verbose_name": "_verbose_name", "db_tablespace": "_db_tablespace", equals_comparison = "choices", "validators" for name, default in possibles.items(): value = getattr(self, attr_overrides.get(name, name)) # Unroll anything iterable for choices into a concrete list if name == "choices" and isinstance(value, collections.Iterable): value = list(value) # Do correct kind of comparison if name in equals_comparison: if value != default: keywords[name] = value else: if value is not default: keywords[name] = value # Work out path - we shorten it for known Django core fields path = "%s.%s" % (self.__class__.__module__, self.__class__.__qualname__) if path.startswith("django.db.models.fields.related"): path = path.replace("django.db.models.fields.related", "django.db.models") if path.startswith("django.db.models.fields.files"): path = path.replace("django.db.models.fields.files", "django.db.models") if path.startswith("django.db.models.fields.proxy"): path = path.replace("django.db.models.fields.proxy", "django.db.models") if path.startswith("django.db.models.fields"): path = path.replace("django.db.models.fields", "django.db.models") # Return basic info - other fields should override this. return (self.name, path, [], keywords) def clone(self): """ Uses deconstruct() to clone a new copy of this Field. Will not preserve any class attachments/attribute names. """ name, path, args, kwargs = self.deconstruct() return self.__class__(*args, **kwargs) def __eq__(self, other): # Needed for @total_ordering if isinstance(other, Field): return self.creation_counter == other.creation_counter return NotImplemented def __lt__(self, other): # This is needed because bisect does not take a comparison function. if isinstance(other, Field): return self.creation_counter < other.creation_counter return NotImplemented def __hash__(self): return hash(self.creation_counter) def __deepcopy__(self, memodict): # We don‘t have to deepcopy very much here, since most things are not # intended to be altered after initial creation. obj = copy.copy(self) if self.remote_field: obj.remote_field = copy.copy(self.remote_field) if hasattr(self.remote_field, ‘field‘) and self.remote_field.field is self: obj.remote_field.field = obj memodict[id(self)] = obj return obj def __copy__(self): # We need to avoid hitting __reduce__, so define this # slightly weird copy construct. obj = Empty() obj.__class__ = self.__class__ obj.__dict__ = self.__dict__.copy() return obj def __reduce__(self): """ Pickling should return the model._meta.fields instance of the field, not a new copy of that field. So, use the app registry to load the model and then the field back. """ if not hasattr(self, ‘model‘): # Fields are sometimes used without attaching them to models (for # example in aggregation). In this case give back a plain field # instance. The code below will create a new empty instance of # class self.__class__, then update its dict with self.__dict__ # values - so, this is very close to normal pickle. state = self.__dict__.copy() # The _get_default cached_property can‘t be pickled due to lambda # usage. state.pop(‘_get_default‘, None) return _empty, (self.__class__,), state return _load_field, (self.model._meta.app_label, self.model._meta.object_name, self.name) def get_pk_value_on_save(self, instance): """ Hook to generate new PK values on save. This method is called when saving instances with no primary key value set. If this method returns something else than None, then the returned value is used when saving the new instance. """ if self.default: return self.get_default() return None def to_python(self, value): """ Convert the input value into the expected Python data type, raising django.core.exceptions.ValidationError if the data can‘t be converted. Return the converted value. Subclasses should override this. """ return value @cached_property def validators(self): """ Some validators can‘t be created at field initialization time. This method provides a way to delay their creation until required. """ return list(itertools.chain(self.default_validators, self._validators)) def run_validators(self, value): if value in self.empty_values: return errors = [] for v in self.validators: try: v(value) except exceptions.ValidationError as e: if hasattr(e, ‘code‘) and e.code in self.error_messages: e.message = self.error_messages[e.code] errors.extend(e.error_list) if errors: raise exceptions.ValidationError(errors) def validate(self, value, model_instance): """ Validate value and raise ValidationError if necessary. Subclasses should override this to provide validation logic. """ if not self.editable: # Skip validation for non-editable fields. return if self.choices and value not in self.empty_values: for option_key, option_value in self.choices: if isinstance(option_value, (list, tuple)): # This is an optgroup, so look inside the group for # options. for optgroup_key, optgroup_value in option_value: if value == optgroup_key: return elif value == option_key: return raise exceptions.ValidationError( self.error_messages[‘invalid_choice‘], code=‘invalid_choice‘, params=‘value‘: value, ) if value is None and not self.null: raise exceptions.ValidationError(self.error_messages[‘null‘], code=‘null‘) if not self.blank and value in self.empty_values: raise exceptions.ValidationError(self.error_messages[‘blank‘], code=‘blank‘) def clean(self, value, model_instance): """ Convert the value‘s type and run validation. Validation errors from to_python() and validate() are propagated. Return the correct value if no error is raised. """ value = self.to_python(value) self.validate(value, model_instance) self.run_validators(value) return value def db_type_parameters(self, connection): return DictWrapper(self.__dict__, connection.ops.quote_name, ‘qn_‘) def db_check(self, connection): """ Return the database column check constraint for this field, for the provided connection. Works the same way as db_type() for the case that get_internal_type() does not map to a preexisting model field. """ data = self.db_type_parameters(connection) try: return connection.data_type_check_constraints[self.get_internal_type()] % data except KeyError: return None def db_type(self, connection): """ Return the database column data type for this field, for the provided connection. """ # The default implementation of this method looks at the # backend-specific data_types dictionary, looking up the field by its # "internal type". # # A Field class can implement the get_internal_type() method to specify # which *preexisting* Django Field class it‘s most similar to -- i.e., # a custom field might be represented by a TEXT column type, which is # the same as the TextField Django field type, which means the custom # field‘s get_internal_type() returns ‘TextField‘. # # But the limitation of the get_internal_type() / data_types approach # is that it cannot handle database column types that aren‘t already # mapped to one of the built-in Django field types. In this case, you # can implement db_type() instead of get_internal_type() to specify # exactly which wacky database column type you want to use. data = self.db_type_parameters(connection) try: return connection.data_types[self.get_internal_type()] % data except KeyError: return None def rel_db_type(self, connection): """ Return the data type that a related field pointing to this field should use. For example, this method is called by ForeignKey and OneToOneField to determine its data type. """ return self.db_type(connection) def cast_db_type(self, connection): """Return the data type to use in the Cast() function.""" db_type = connection.ops.cast_data_types.get(self.get_internal_type()) if db_type: return db_type % self.db_type_parameters(connection) return self.db_type(connection) def db_parameters(self, connection): """ Extension of db_type(), providing a range of different return values (type, checks). This will look at db_type(), allowing custom model fields to override it. """ type_string = self.db_type(connection) check_string = self.db_check(connection) return "type": type_string, "check": check_string, def db_type_suffix(self, connection): return connection.data_types_suffix.get(self.get_internal_type()) def get_db_converters(self, connection): if hasattr(self, ‘from_db_value‘): return [self.from_db_value] return [] @property def unique(self): return self._unique or self.primary_key @property def db_tablespace(self): return self._db_tablespace or settings.DEFAULT_INDEX_TABLESPACE def set_attributes_from_name(self, name): if not self.name: self.name = name self.attname, self.column = self.get_attname_column() self.concrete = self.column is not None if self.verbose_name is None and self.name: self.verbose_name = self.name.replace(‘_‘, ‘ ‘) def contribute_to_class(self, cls, name, private_only=False): """ Register the field with the model class it belongs to. If private_only is True, create a separate instance of this field for every subclass of cls, even if cls is not an abstract model. """ self.set_attributes_from_name(name) self.model = cls if private_only: cls._meta.add_field(self, private=True) else: cls._meta.add_field(self) if self.column: # Don‘t override classmethods with the descriptor. This means that # if you have a classmethod and a field with the same name, then # such fields can‘t be deferred (we don‘t have a check for this). if not getattr(cls, self.attname, None): setattr(cls, self.attname, DeferredAttribute(self.attname, cls)) if self.choices: setattr(cls, ‘get_%s_display‘ % self.name, partialmethod(cls._get_FIELD_display, field=self)) def get_filter_kwargs_for_object(self, obj): """ Return a dict that when passed as kwargs to self.model.filter(), would yield all instances having the same value for this field as obj has. """ return self.name: getattr(obj, self.attname) def get_attname(self): return self.name def get_attname_column(self): attname = self.get_attname() column = self.db_column or attname return attname, column def get_internal_type(self): return self.__class__.__name__ def pre_save(self, model_instance, add): """Return field‘s value just before saving.""" return getattr(model_instance, self.attname) def get_prep_value(self, value): """Perform preliminary non-db specific value checks and conversions.""" if isinstance(value, Promise): value = value._proxy____cast() return value def get_db_prep_value(self, value, connection, prepared=False): """ Return field‘s value prepared for interacting with the database backend. Used by the default implementations of get_db_prep_save(). """ if not prepared: value = self.get_prep_value(value) return value def get_db_prep_save(self, value, connection): """Return field‘s value prepared for saving into a database.""" return self.get_db_prep_value(value, connection=connection, prepared=False) def has_default(self): """Return a boolean of whether this field has a default value.""" return self.default is not NOT_PROVIDED def get_default(self): """Return the default value for this field.""" return self._get_default() @cached_property def _get_default(self): if self.has_default(): if callable(self.default): return self.default return lambda: self.default if not self.empty_strings_allowed or self.null and not connection.features.interprets_empty_strings_as_nulls: return return_None return str # return empty string def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_choices_to=None): """ Return choices with a default blank choices included, for use as <select> choices for this field. """ blank_defined = False choices = list(self.choices) if self.choices else [] named_groups = choices and isinstance(choices[0][1], (list, tuple)) if not named_groups: for choice, __ in choices: if choice in (‘‘, None): blank_defined = True break first_choice = (blank_choice if include_blank and not blank_defined else []) if self.choices: return first_choice + choices rel_model = self.remote_field.model limit_choices_to = limit_choices_to or self.get_limit_choices_to() if hasattr(self.remote_field, ‘get_related_field‘): lst = [(getattr(x, self.remote_field.get_related_field().attname), smart_text(x)) for x in rel_model._default_manager.complex_filter( limit_choices_to)] else: lst = [(x.pk, smart_text(x)) for x in rel_model._default_manager.complex_filter( limit_choices_to)] return first_choice + lst def value_to_string(self, obj): """ Return a string value of this field from the passed obj. This is used by the serialization framework. """ return str(self.value_from_object(obj)) def _get_flatchoices(self): """Flattened version of choices tuple.""" flat = [] for choice, value in self.choices: if isinstance(value, (list, tuple)): flat.extend(value) else: flat.append((choice, value)) return flat flatchoices = property(_get_flatchoices) def save_form_data(self, instance, data): setattr(instance, self.name, data) def formfield(self, form_class=None, choices_form_class=None, **kwargs): """Return a django.forms.Field instance for this field.""" defaults = ‘required‘: not self.blank, ‘label‘: capfirst(self.verbose_name), ‘help_text‘: self.help_text if self.has_default(): if callable(self.default): defaults[‘initial‘] = self.default defaults[‘show_hidden_initial‘] = True else: defaults[‘initial‘] = self.get_default() if self.choices: # Fields with choices get special treatment. include_blank = (self.blank or not (self.has_default() or ‘initial‘ in kwargs)) defaults[‘choices‘] = self.get_choices(include_blank=include_blank) defaults[‘coerce‘] = self.to_python if self.null: defaults[‘empty_value‘] = None if choices_form_class is not None: form_class = choices_form_class else: form_class = forms.TypedChoiceField # Many of the subclass-specific formfield arguments (min_value, # max_value) don‘t apply for choice fields, so be sure to only pass # the values that TypedChoiceField will understand. for k in list(kwargs): if k not in (‘coerce‘, ‘empty_value‘, ‘choices‘, ‘required‘, ‘widget‘, ‘label‘, ‘initial‘, ‘help_text‘, ‘error_messages‘, ‘show_hidden_initial‘, ‘disabled‘): del kwargs[k] defaults.update(kwargs) if form_class is None: form_class = forms.CharField return form_class(**defaults) def value_from_object(self, obj): """Return the value of this field in the given model instance.""" return getattr(obj, self.attname)
- ForeighKey字段对象
#判断一个字段对象是Foreignkey通过get_internal_type方法 In [66]: f2 = models.Book._meta.get_field("publish") In [67]: f2.get_internal_type() Out[67]: ‘ForeignKey‘ #使用时这样使用 if field_obj.get_internal_type() == "ForeignKey": pass #另外一种判断方法是通过isinstance方法 from django.db.models import ForeignKey if isinstance(_field_obj, ForeignKey): pass #通过外键字段取出另一张表的内容,通过remote_field In [68]: f2.remote_field Out[68]: <ManyToOneRel: app01.book> #外键在那一张表,那一张表就是多 In [69]: f2.remote_field.model Out[69]: app01.models.Publish #可以通过f2.remote_field.model.objects.all()
- ManyToMany字段对象
#判断一个字段对象是ManyToMany字段对象的两种方法 #获取ManyToMany字段对象 In [3]: models.Book._meta.get_field("authors") Out[3]: <django.db.models.fields.related.ManyToManyField: authors> #1、通过get_internal_type()方法 In [4]: f3 = models.Book._meta.get_field("authors") In [5]: f3.get_internal_type() Out[5]: ‘ManyToManyField‘ #2、通过isinstance判断 from django.db.models import ManyToManyField if isinstance(_field_obj,ManyToManyField): pass ##通过ManyToMany字段对象取出另一张表的内容(remote_field) In [6]: f3.remote_field Out[6]: <ManyToManyRel: app01.book> In [7]: f3.remote_field.model.objects.all() Out[7]: <QuerySet [<Author: 张三>, <Author: 李四>, <Author: 王五>]>
- 字段参数中含有choice
例如:
payment_type_choices = [ (1, "定金"), (2, "尾款"), (3, "其它"), ] payment=models.IntegerField(choices=payment_type_choices,verbose_name=‘付款类型‘)
#获取choice参数的字段对象 In [1]: from crm import models In [2]: models.PaymentRecord._meta.get_field("payment") Out[2]: <django.db.models.fields.IntegerField: payment> #判断该字段对象是否有choice选项参数 In [3]: f4 = models.PaymentRecord._meta.get_field("payment") In [4]: f4.choices Out[5]: [(1, ‘定金‘), (2, ‘尾款‘), (3, ‘其它‘)] #如果炫耀这样的借口,可以使用get_choices方法 In [6]: f4.get_choices() Out[6]: [(‘‘, ‘---------‘), (1, ‘定金‘), (2, ‘尾款‘), (3, ‘其它‘)]
二、forms字段对象
在介绍forms字段对象还是有必要了解一下BoundField类,详细可以参考:https://www.cnblogs.com/shenjianping/p/11548311.html
(一)BoundField
先来一个实例:
from django import forms from django.forms.boundfield import BoundField from app01 import models class BookForm(forms.Form): title=forms.CharField(max_length=32) price=forms.IntegerField() pub_date=forms.DateField(
widget = forms.widgets.TextInput(attrs=‘type‘:‘date‘)
) publish=forms.ModelChoiceField(queryset=models.Publish.objects.all()) authors=forms.ModelMultipleChoiceField(queryset=models.Author.objects.all())
BoundField对象就是循环BookForm实例对象中的每一个对象,或者根据forms字段名取出的每一个对象:
In [1]: from app01.forms import BookForm In [2]: form = BookForm() In [14]: form["publish"] Out[14]: <django.forms.boundfield.BoundField at 0x7027710>
在BoundField对象中有很多属性和方法:
class BoundField: "A Field plus data" def __init__(self, form, field, name): self.form = form self.field = field self.name = name self.html_name = form.add_prefix(name) self.html_initial_name = form.add_initial_prefix(name) self.html_initial_id = form.add_initial_prefix(self.auto_id) if self.field.label is None: self.label = pretty_name(name) else: self.label = self.field.label self.help_text = field.help_text or ‘‘ def __str__(self): """Render this field as an HTML widget.""" if self.field.show_hidden_initial: return self.as_widget() + self.as_hidden(only_initial=True) return self.as_widget() @cached_property def subwidgets(self): """ Most widgets yield a single subwidget, but others like Radioselect and CheckboxSelectMultiple produce one subwidget for each choice. This property is cached so that only one database query occurs when rendering ModelChoiceFields. """ id_ = self.field.widget.attrs.get(‘id‘) or self.auto_id attrs = ‘id‘: id_ if id_ else attrs = self.build_widget_attrs(attrs) return [ BoundWidget(self.field.widget, widget, self.form.renderer) for widget in self.field.widget.subwidgets(self.html_name, self.value(), attrs=attrs) ] def __bool__(self): # BoundField evaluates to True even if it doesn‘t have subwidgets. return True def __iter__(self): return iter(self.subwidgets) def __len__(self): return len(self.subwidgets) def __getitem__(self, idx): # Prevent unnecessary reevaluation when accessing BoundField‘s attrs # from templates. if not isinstance(idx, (int, slice)): raise TypeError return self.subwidgets[idx] @property def errors(self): """ Return an ErrorList (empty if there are no errors) for this field. """ return self.form.errors.get(self.name, self.form.error_class()) def as_widget(self, widget=None, attrs=None, only_initial=False): """ Render the field by rendering the passed widget, adding any HTML attributes passed as attrs. If a widget isn‘t specified, use the field‘s default widget. """ if not widget: widget = self.field.widget if self.field.localize: widget.is_localized = True attrs = attrs or attrs = self.build_widget_attrs(attrs, widget) auto_id = self.auto_id if auto_id and ‘id‘ not in attrs and ‘id‘ not in widget.attrs: if not only_initial: attrs[‘id‘] = auto_id else: attrs[‘id‘] = self.html_initial_id if not only_initial: name = self.html_name else: name = self.html_initial_name kwargs = if func_supports_parameter(widget.render, ‘renderer‘) or func_accepts_kwargs(widget.render): kwargs[‘renderer‘] = self.form.renderer else: warnings.warn( ‘Add the `renderer` argument to the render() method of %s. ‘ ‘It will be mandatory in Django 2.1.‘ % widget.__class__, RemovedInDjango21Warning, stacklevel=2, ) return widget.render( name=name, value=self.value(), attrs=attrs, **kwargs ) def as_text(self, attrs=None, **kwargs): """ Return a string of HTML for representing this as an <input type="text">. """ return self.as_widget(TextInput(), attrs, **kwargs) def as_textarea(self, attrs=None, **kwargs): """Return a string of HTML for representing this as a <textarea>.""" return self.as_widget(Textarea(), attrs, **kwargs) def as_hidden(self, attrs=None, **kwargs): """ Return a string of HTML for representing this as an <input type="hidden">. """ return self.as_widget(self.field.hidden_widget(), attrs, **kwargs) @property def data(self): """ Return the data for this BoundField, or None if it wasn‘t given. """ return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) def value(self): """ Return the value for this BoundField, using the initial value if the form is not bound or the data otherwise. """ data = self.initial if self.form.is_bound: data = self.field.bound_data(self.data, data) return self.field.prepare_value(data) def label_tag(self, contents=None, attrs=None, label_suffix=None): """ Wrap the given contents in a <label>, if the field has an ID attribute. contents should be mark_safe‘d to avoid HTML escaping. If contents aren‘t given, use the field‘s HTML-escaped label. If attrs are given, use them as HTML attributes on the <label> tag. label_suffix overrides the form‘s label_suffix. """ contents = contents or self.label if label_suffix is None: label_suffix = (self.field.label_suffix if self.field.label_suffix is not None else self.form.label_suffix) # Only add the suffix if the label does not end in punctuation. # Translators: If found as last label character, these punctuation # characters will prevent the default label_suffix to be appended to the label if label_suffix and contents and contents[-1] not in _(‘:?.!‘): contents = format_html(‘‘, contents, label_suffix) widget = self.field.widget id_ = widget.attrs.get(‘id‘) or self.auto_id if id_: id_for_label = widget.id_for_label(id_) if id_for_label: attrs = dict(attrs or , **‘for‘: id_for_label) if self.field.required and hasattr(self.form, ‘required_css_class‘): attrs = attrs or if ‘class‘ in attrs: attrs[‘class‘] += ‘ ‘ + self.form.required_css_class else: attrs[‘class‘] = self.form.required_css_class attrs = flatatt(attrs) if attrs else ‘‘ contents = format_html(‘<label></label>‘, attrs, contents) else: contents = conditional_escape(contents) return mark_safe(contents) def css_classes(self, extra_classes=None): """ Return a string of space-separated CSS classes for this field. """ if hasattr(extra_classes, ‘split‘): extra_classes = extra_classes.split() extra_classes = set(extra_classes or []) if self.errors and hasattr(self.form, ‘error_css_class‘): extra_classes.add(self.form.error_css_class) if self.field.required and hasattr(self.form, ‘required_css_class‘): extra_classes.add(self.form.required_css_class) return ‘ ‘.join(extra_classes) @property def is_hidden(self): """Return True if this BoundField‘s widget is hidden.""" return self.field.widget.is_hidden @property def auto_id(self): """ Calculate and return the ID attribute for this BoundField, if the associated Form has specified auto_id. Return an empty string otherwise. """ auto_id = self.form.auto_id # Boolean or string if auto_id and ‘%s‘ in str(auto_id): return auto_id % self.html_name elif auto_id: return self.html_name return ‘‘ @property def id_for_label(self): """ Wrapper around the field widget‘s `id_for_label` method. Useful, for example, for focusing on this field regardless of whether it has a single widget or a MultiWidget. """ widget = self.field.widget id_ = widget.attrs.get(‘id‘) or self.auto_id return widget.id_for_label(id_) @cached_property def initial(self): data = self.form.get_initial_for_field(self.field, self.name) # If this is an auto-generated default date, nix the microseconds for # standardized handling. See #22502. if (isinstance(data, (datetime.datetime, datetime.time)) and not self.field.widget.supports_microseconds): data = data.replace(microsecond=0) return data def build_widget_attrs(self, attrs, widget=None): if not widget: widget = self.field.widget attrs = dict(attrs) # Copy attrs to avoid modifying the argument. if widget.use_required_attribute(self.initial) and self.field.required and self.form.use_required_attribute: attrs[‘required‘] = True if self.field.disabled: attrs[‘disabled‘] = True return attrs
其中,有两个经常用到的属性name,field:
In [10]: b2 = form["publish"] ##field就是forms中的每一个字段对象 In [11]: b2.field Out[11]: <django.forms.models.ModelChoiceField at 0x6f032b0> ##name就是forms字段的名称 In [15]: b2.name Out[15]: ‘publish‘
(二)forms特殊字段对象
1、ForeignKey、ManyToMany
另外,还有一个可能用到的知识点就是根据form字段对象来判断ForeignKey和ManyToMany,并且取出model_name,app_label
#判断forms字段对象ForeignKey或者ManyToMany from django.forms.models import ModelChoiceField if isinstance(bfield.field,ModelChoiceField): #注意bfield是BoundField对象 pass #取出app_label,model_name,这里只针对ForeignKey和ManyToMany,因为只有它们才有queryset参数 app_label = bfield.field.queryset.model._meta.app_label model_name = bfield.field.queryset.model._meta.model_name ###测试#### In [10]: b2 = form["publish"] In [11]: b2.field Out[11]: <django.forms.models.ModelChoiceField at 0x6f032b0> In [14]: form["publish"] Out[14]: <django.forms.boundfield.BoundField at 0x7027710> In [16]: b2.field.queryset.model._meta.app_label Out[16]: ‘app01‘ In [17]: b2.field.queryset.model._meta.model_name Out[17]: ‘publish‘
最后,还可以取出ForeignKey或者ManyToMamy关联的表中的数据:
#取出关联表中的数据 In [18]: b2.field.queryset.model.objects.all() Out[18]: <QuerySet [<Publish: 北京出版社>, <Publish: 天津出版社>]> #或者更为简单 In [20]: b2.field.queryset Out[20]: <QuerySet [<Publish: 北京出版社>, <Publish: 天津出版社>]>
2、含有choices参数的字段
class BookForm(forms.Form): ... book_type=forms.ChoiceField(choices=((1,"自然科学"),(2,"人文社科"),(3,"其它"))) ...
forms字段中使用ChoiceField这种字段来处理带有choices参数的字段,而在model中使用IntegerField,在其源码中也有很多属性和方法使用:
class ChoiceField(Field): widget = Select default_error_messages = ‘invalid_choice‘: _(‘Select a valid choice. %(value)s is not one of the available choices.‘), def __init__(self, *, choices=(), **kwargs): super().__init__(**kwargs) self.choices = choices def __deepcopy__(self, memo): result = super().__deepcopy__(memo) result._choices = copy.deepcopy(self._choices, memo) return result def _get_choices(self): return self._choices def _set_choices(self, value): # Setting choices also sets the choices on the widget. # choices can be any iterable, but we call list() on it because # it will be consumed more than once. if callable(value): value = CallableChoiceIterator(value) else: value = list(value) self._choices = self.widget.choices = value choices = property(_get_choices, _set_choices) def to_python(self, value): """Return a string.""" if value in self.empty_values: return ‘‘ return str(value) def validate(self, value): """Validate that the input is in self.choices.""" super().validate(value) if value and not self.valid_value(value): raise ValidationError( self.error_messages[‘invalid_choice‘], code=‘invalid_choice‘, params=‘value‘: value, ) def valid_value(self, value): """Check to see if the provided value is a valid choice.""" text_value = str(value) for k, v in self.choices: if isinstance(v, (list, tuple)): # This is an optgroup, so look inside the group for options for k2, v2 in v: if value == k2 or text_value == str(k2): return True else: if value == k or text_value == str(k): return True return False
#判断forms字段对象是否是ChoiceField In [2]: form = BookForm() In [3]: form["book_type"] Out[3]: <django.forms.boundfield.BoundField at 0x6eca400> In [4]: b4 = form["book_type"] In [7]: b4.field Out[7]: <django.forms.fields.ChoiceField at 0x6edf668> In [8]: b4.field.choices Out[8]: [(1, ‘自然科学‘), (2, ‘人文社科‘), (3, ‘其它‘)] #所以可以通过以下进行判断,同时也可以取到值 if b4.field.choices: pass #当然也可以通过方法取值 In [12]: b4.field._get_choices() Out[12]: [(1, ‘自然科学‘), (2, ‘人文社科‘), (3, ‘其它‘)]
三、总结
- forms字段中的ForeignKey与ManyToMany字段对象依靠queryset参数可以与model关联上
- 如果是修改表单的Form,可以通过form.instance获取model实例,因为在修改的Form中回传入instance参数
以上是关于model字段对象和forms字段对象的区别和联系的主要内容,如果未能解决你的问题,请参考以下文章
Django---FORM组件.FORM组件的字段,FORM组件校验流程,FORM组件的全局和局部钩子,FORM和Model的组合