上QQ阅读APP看书,第一时间看更新
How to do it...
- Open the models.py file in the utils package in a text editor, and insert the following content there:
# utils/models.py
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldError
from django.db import models
from django.utils.translation import ugettext_lazy as _
def object_relation_mixin_factory(
prefix=None,
prefix_verbose=None,
add_related_name=False,
limit_content_type_choices_to=None,
limit_object_choices_to=None,
is_required=False):
"""
returns a mixin class for generic foreign keys using
"Content type - object Id" with dynamic field names.
This function is just a class generator
Parameters:
prefix: a prefix, which is added in front of
the fields
prefix_verbose: a verbose name of the prefix, used to
generate a title for the field column
of the content object in the Admin
add_related_name: a boolean value indicating, that a
related name for the generated content
type foreign key should be added. This
value should be true, if you use more
than one ObjectRelationMixin in your
model.
The model fields are created like this:
<<prefix>>_content_type: Field name for the "content type"
<<prefix>>_object_id: Field name for the "object id"
<<prefix>>_content_object: Field name for the "content object"
"""
p = ""
if prefix:
p = f"{prefix}_"
prefix_verbose = prefix_verbose or _("Related object")
limit_content_type_choices_to = (limit_content_type_choices_to
or {})
limit_object_choices_to = limit_object_choices_to or {}
content_type_field = f"{p}content_type"
object_id_field = f"{p}object_id"
content_object_field = f"{p}content_object"
class TheClass(models.Model):
class Meta:
abstract = True
if add_related_name:
if not prefix:
raise FieldError("if add_related_name is set to "
"True, a prefix must be given")
related_name = prefix
else:
related_name = None
optional = not is_required
ct_verbose_name = _(f"{prefix_verbose}'s type (model)")
content_type = models.ForeignKey(
ContentType,
verbose_name=ct_verbose_name,
related_name=related_name,
blank=optional,
null=optional,
help_text=_("Please select the type (model) "
"for the relation, you want to build."),
limit_choices_to=limit_content_type_choices_to,
on_delete=models.CASCADE)
fk_verbose_name = prefix_verbose
object_id = models.CharField(
fk_verbose_name,
blank=optional,
null=False,
help_text=_("Please enter the ID of the related object."),
max_length=255,
default="") # for migrations
object_id.limit_choices_to = limit_object_choices_to
# can be retrieved by
# MyModel._meta.get_field("object_id").limit_choices_to
content_object = generic.GenericForeignKey(
ct_field=content_type_field,
fk_field=object_id_field)
TheClass.add_to_class(content_type_field, content_type)
TheClass.add_to_class(object_id_field, object_id)
TheClass.add_to_class(content_object_field,
content_object)
return TheClass
- The following code snippet is an example of how to use two generic relationships in your app (put this code in demo_app/models.py):
# demo_app/models.py
from django.db import models
from utils.models import (
object_relation_mixin_factory as generic_relation)
FavoriteObjectMixin = generic_relation(is_required=True)
OwnerMixin = generic_relation(
prefix="owner",
prefix_verbose=_("Owner"),
is_required=True,
add_related_name=True,
limit_content_type_choices_to={
'model__in': ('user', 'institution')
})
class Like(FavoriteObjectMixin, OwnerMixin):
class Meta:
verbose_name = _("Like")
verbose_name_plural = _("Likes")
def __str__(self):
return _("%(owner)s likes %(obj)s") % {
"owner": self.owner_content_object,
"obj": self.content_object,
}