如何在django中扩展Site模型?

时间:2010-05-12 18:33:03

标签: python django django-models

在django中扩展站点模型的最佳方法是什么?创建一个新模型和ForeignKey网站还是有另一种方法可以让我继承Site模型?

我更喜欢子类化,因为从关系上来说我更舒服,但我担心它对内置管理员的影响。

6 个答案:

答案 0 :(得分:8)

我刚使用自己的Site子类并为其创建了自定义管理员。

基本上,当您在django中对模型进行子类化时,它会创建指向父模型的FK,并允许透明地访问父模型的字段 - 就像您在pyhon中访问父类属性一样。 内置管理员不会受到任何影响,但您必须取消注册Sites ModelAdmin并注册您自己的ModelAdmin。

答案 1 :(得分:4)

如果您只想更改对象的行为,但不想添加任何新字段,则应考虑使用“代理模型”(Django 1.1中的新增功能)。您可以向现有模型添加额外的Python方法,以及更多:

  

这就是代理模型继承的用途:为原始模型创建代理。您可以创建,删除和更新代理模型的实例,并且将保存所有数据,就像使用原始(非代理)模型一样。不同之处在于您可以更改代理中的默认模型排序或默认管理器,而无需更改原始文件。

the documentation中阅读更多内容。

答案 2 :(得分:2)

您可以使用其他模型,例如SiteProfileSite具有OneToOne关系。

答案 3 :(得分:0)

从Django 2.2开始,仍然没有像Site那样扩展User的简单直接方法。现在最好的方法是创建新实体,并在其中放置参数。如果您想利用existing sites support,这是唯一的方法。

class SiteProfile(models.Model):
    title = models.TextField()
    site = models.OneToOneField(Site)

您将必须为SiteProfile创建管理员。然后添加带有链接的SiteProfile的一些Site记录。现在,您可以在任何可以通过模型访问当前站点的地方使用site.siteprofile.title

答案 4 :(得分:0)

问这个问题已经有很长时间了,但我认为(Django 3.1)还没有像创建自定义用户模型那样的简单解决方案。在这种情况下,创建一个继承自django.contrib.auth.models.AbstractUser模型的自定义用户模型,并将AUTH_USER_MODEL(在设置中)更改为新创建的自定义用户模型即可解决此问题。

但是,对于Site模型,也可以通过以下详细解决方案来实现:

解决方案

假设您有一个名称为core的应用程序。使用该应用程序执行以下所有代码(设置文件除外)。

  1. 使用与Site模型具有OneToOne关系的site字段创建SiteProfile模型。我还更改了其app_label元,以便可以在管理员的“网站”应用下看到它。
# in core.models

...
from django.contrib.sites.models import Site
from django.db import models

class SiteProfile(models.Model):
    """SiteProfile model is OneToOne related to Site model."""
    site = models.OneToOneField(
        Site, on_delete=models.CASCADE, primary_key=True,
        related_name='profiles', verbose_name='site')

    long_name = models.CharField(
        max_length=255, blank=True, null=True)
    meta_name = models.CharField(
        max_length=255, blank=True, null=True)

    def __str__(self):
        return self.site.name

    class Meta:
        app_label = 'sites'  # make it under sites app (in admin)

...
  1. 在管理员中注册模型。 (在core.admin中)

如果您只想创建一个网站配置文件模型,那么到目前为止我们所做的工作已经足够了。但是,您将希望在迁移后立即创建第一个配置文件。因为创建了第一个站点,但没有创建与之相关的第一个配置文件。如果您不想手动创建它,则需要执行第三步。

  1. 在core.apps.py中编写以下代码:
# in core.apps

...
from django.conf import settings
from django.db.models.signals import post_migrate

def create_default_site_profile(sender, **kwargs):
    """after migrations"""
    from django.contrib.sites.models import Site
    from core.models import SiteProfile

    site = Site.objects.get(id=getattr(settings, 'SITE_ID', 1))

    if not SiteProfile.objects.exists():
        SiteProfile.objects.create(site=site)

class CoreConfig(AppConfig):
    name = 'core'

    def ready(self):
        post_migrate.connect(create_default_site_profile, sender=self)
        from .signals import (create_site_profile)  # now create the second signal

函数(create_default_site_profile)将使用post_migrate信号自动创建与迁移后的第一个站点相关的第一个配置文件。但是,您将需要另一个信号(post_save),即上述代码的最后一行。

  1. 如果执行此步骤,您的SiteProfile模型将与Site模型完全连接。创建/更新任何Site对象时,都会自动创建/更新一个SiteProfile对象。信号从apps.py的最后一行调用。
# in core.signals

from django.contrib.sites.models import Site
from django.db.models.signals import post_save, post_migrate
from django.dispatch import receiver

from .models import SiteProfile


@receiver(post_save, sender=Site)
def create_site_profile(sender, instance, **kwargs):
    """This signal creates/updates a SiteProfile object 
    after creating/updating a Site object.
    """
    siteprofile, created = SiteProfile.objects.update_or_create(
        site=instance
    )

    if not created:
        siteprofile.save()


您想在模板上使用它吗?例如 {{ site.name }}

然后您需要执行第5步和第6步。

  1. 在settings.py>模板>选项> context_processors中添加以下代码 'core.context_processors.site_processor'
# in settings.py

TEMPLATES = [
    {
        # ...
        'OPTIONS': {
            'context_processors': [
                # ...

                # custom processor for getting the current site
                'core.context_processors.site_processor',
            ],
        },
    },
]
  1. 使用以下代码在核心应用中创建一个context_processors.py文件。 需要try-catch块(捕获部分)以使其更安全。如果您从数据库中删除所有站点,则在admin和前端页面上都会出现错误。错误为Site matching query does not exist.,因此catch块如果为空,则会创建一个。

如果您有第二个站点并将其删除,则此解决方案可能不完全合格。此解决方案仅创建一个ID = 1的网站。

# in core.context_processors

from django.conf import settings
from django.contrib.sites.models import Site


def site_processor(request):
    try:
        return {
            'site': Site.objects.get_current()
        }
    except:
        Site.objects.create(
            id=getattr(settings, 'SITE_ID', 1),
            domain='example.com', name='example.com')

您现在可以在模板中使用站点名称,域,元名称,长名称或添加的任何字段。

# e.g.
{{ site.name }} 
{{ site.profiles.long_name }} 

通常会添加两个数据库查询,一个用于File.objects,一个用于FileProfile.objects。但是,正如文档中所述, Django is clever enough to cache the current site at the first request and it serves the cached data at the subsequent calls.

https://docs.djangoproject.com/en/3.1/ref/contrib/sites/#caching-the-current-site-object

答案 5 :(得分:0)

显然,您还可以在添加到 INSTALLED_APPS 的文件夹中创建一个 models.py 文件,内容如下:

from django.contrib.sites.models import Site as DjangoSite, SiteManager    
from django.core.exceptions import ImproperlyConfigured    
from django.db import models    
from django.http.request import split_domain_port    
    

# our site model
class Site(DjangoSite):    
    settings = models.JSONField(blank=True, default={})    
    port = models.PositiveIntegerField(null=True)    
    protocol = models.CharField(default='http', max_length=5)    
    
    @property    
    def url(self):    
        if self.port:    
            host = f'{self.domain}:{self.port}'    
        else:    
            host = self.domain    
        return f'{self.protocol}://{host}/'    
    

# patch django.contrib.sites.models.Site.objects to use our Site class
DjangoSite.objects.model = Site      
    

# optionnal: override get_current to auto create site instances 
old_get_current = SiteManager.get_current    
def get_current(self, request=None):    
    try:     
        return old_get_current(self, request)    
    except (ImproperlyConfigured, Site.DoesNotExist):    
        if not request:    
            return Site(domain='localhost', name='localhost')    
        host = request.get_host()    
        domain, port = split_domain_port(host)    
        Site.objects.create(    
            name=domain.capitalize(),    
            domain=host,    
            port=port,    
            protocol=request.META['wsgi.url_scheme'],    
        )    
    return old_get_current(self, request)    
SiteManager.get_current = get_current