保存后立即为产品和公司保存 slug

save slug for product and company as soon as they are saved

我有一个产品和公司模型,其中包含 slug 以便在 url 中获得更好的详细视图。一旦产品和公司被保存到数据库中,我就使用 pre_save 信号来保存 slug。我写的代码没有保存 slug 所以当我 post 产品表格时我收到关于 slug

的错误

这是我的代码

class Product(models.Model):
    name = models.CharField(max_length=200, unique=True, blank=False, null=False)
    company = models.ForeignKey('Company', related_name='products', blank=True, null=True, on_delete=models.SET_NULL)
    website = models.URLField(unique=True)
    slug = models.SlugField(unique=True)

    class Meta:
        verbose_name= 'Product'
        verbose_name_plural= 'Products'

    def __str__(self):
        return self.name

    def hits(self):
        self.hits += 1
        self.save(update_fields=['hits'])

class Company(models.Model):
    name =  models.CharField(max_length=200, unique=True, blank=False, null=False)
    slug = models.SlugField(unique=True)
    description = models.CharField(max_length=400)
    editor = models.ForeignKey(User, related_name='company')
    # product = models.ForeignKey(Product, related_name='company')

    def get_absolute_url(self):
        return reverse("products:view-company", kwargs={"slug": self.slug})


def create_slug(instance, new_slug=None):
    slug = slugify(instance.name)
    if new_slug is not None:
        slug = new_slug
    qs = Company.objects.filter(slug=slug).order_by('-id')
    if qs.exists():
        new_slug = "%s-%S" %(slug, qs.first().id)
        return create_slug(instance, slug=new_slug)
    return slug

def pre_save_slug_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = create_slug(instance)

from django.db.models.signals import pre_save
pre_save.connect(pre_save_slug_receiver, sender=Company)



def create_slug(instance, new_slug=None):
    slug = slugify(instance.name)
    if new_slug is not None:
        slug = new_slug
    qs = Product.objects.filter(slug=slug).order_by('-id')
    if qs.exists():
        new_slug = "%s-%S" %(slug, qs.first().id)
        return create_slug(instance, slug=new_slug)
    return slug

def pre_save_slug_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = create_slug(instance)

from django.db.models.signals import pre_save
pre_save.connect(pre_save_slug_receiver, sender=Product)

我有一组用于通用属性(如 slug 字段)的基本混合。您可以在模型和视图中使用 mixins 将公共属性或方法移动到共享(mixin)class; https://docs.djangoproject.com/en/1.11/topics/class-based-views/mixins/

查看以下代码,它会向使用它的模型添加一个 slug 字段,并在保存时设置 slug。本质上,您可以将整个块复制并粘贴到 python 文件中,然后将其导入您的模型中,例如 class MyModel(SlugMixin, models.Model): 然后您的模型默认有一个 slug 字段。

# -*- coding: utf-8 -*-
"""
.. module:: base.models.slug
   :synopsis: SlugMixin abstract model-mixin

.. moduleauthor:: Mark Walker
"""

# pylint: disable=model-missing-unicode

from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from base.utils.slugify import slugify


class SlugMixin(models.Model):
    """
    Mixin for models that require a slug field. Defaults to using the
    'name' attribute of the model. Can be overridden in the Meta class by
    using 'slug_field' to indicate to the pre_save signal which field to use.

    Example (using 'title' as an alternate slug field);

    class Thing(SlugMixin, models.Model):
        title = models.CharField()
        class Meta:
            slug_field = 'title'
    """
    slug = models.SlugField(
        verbose_name=_('Slug'),
        unique=True,
        db_index=True,
        blank=True,
        default=None,
        help_text=_('This field will be auto-populated if left empty.'),
        max_length=255,
        null=True,
    )

    class Meta:
        """Metadata for the SlugMixin class"""
        abstract = True


@receiver(pre_save)
def slug_mixin_pre_save(sender, instance, **kwargs):
    """
    Automatically populate the slug field of SlugMixin models if the
    name field is populated.

    :param sender: Sender class
    :type instance: SlugMixin
    :param instance: Model instance
    :type instance: SlugMixin
    :param kwargs: Not used
    :return: None
    """
    if issubclass(sender, SlugMixin):
        # check for presence of 'slug_field' in object's meta.
        try:
            # use any named slug_field in the model metadata
            field_name = getattr(instance, '_meta').slug_field
        except AttributeError:
            # default to using the 'name' field
            field_name = 'name'

        # Note: we don't handle any AttributeErrors here, because we
        # WANT to inform the developer that the field he's specified
        # does not exist against his model (or the default).
        field = getattr(instance, field_name)

        if instance.slug is None \
                or instance.slug == "" \
                and field is not None \
                and unicode(field) != u"":

            instance.slug = slugify(
                unicode(field),
                instance=instance,
                slug_field='slug',
            )

上面依赖于下面的方法,但是django在django.utils.text中有自己的slugify方法,但是如果你是初学者,这看起来很复杂所以我会专注于SlugMixin

import re
import unicodedata


def slugify(s, instance=None, slug_field='slug', filter_dict=None, unique=True):
    """
    Slugify the string 's' stored on the specified field on instance such
    that it is unique among all instances

    :param s: the string the slugify
    :type s: str or unicode
    :param instance: optional instance that the string to slugify is stored on
                     (used to exclude self when searching for duplicates)
    :type instance: django.db.models.Model or NoneType
    :param slug_field: name of the field on the model-class that any specified
                       instance belongs to where the slug is stored. Defaults
                       to ``'slug'``.
    :type slug_field: str
    :param filter_dict: optional set of kwargs used to filter instances when
                        checking the uniqueness of any generated slug.
    :type filter_dict: dict or NoneType
    :param unique: if set to ``True`` (the default), we'll generate unique
                   slugs, checking for slug-clashes with other instances of
                   the same model-class as instance. If ``False``, we'll
                   simply slugify and return without checking for uniqueness.
    :type unique: bool
    :return: unicode -- the slugified version of the string ``'s'``.
    """
    # slugify the input string 's'
    s = unicodedata.normalize(
        'NFKD', s).encode('ascii', 'ignore').decode('ascii')
    s = re.sub(r'[^\w\s-]', '', s).strip().lower()
    s = re.sub(r'[-\s]+', '-', s)

    slug = s

    if instance and unique:
        # we have an instance and a request to make a unique slug...check
        # for conflicting slugs among instances of the same model-class,
        # keep counting until we find an unused slug.
        def get_queryset():
            """
            Return a QuerySet that checks for conflicts with the current
            version of the slug string under consideration
            """
            manager = getattr(instance.__class__, '_default_manager')
            if hasattr(manager, 'get_admin_query_set'):
                queryset = manager.get_admin_query_set().filter(
                    **{slug_field: slug})
            else:
                queryset = manager.filter(**{slug_field: slug})

            if filter_dict:
                queryset = queryset.filter(**filter_dict)

            if hasattr(instance, 'id') and instance.id:
                queryset = queryset.exclude(pk=instance.id)

            return queryset

        counter = 1
        while get_queryset():
            counter += 1
            slug = "%s-%s" % (s, counter)

    # done!
    return slug