你如何在 django 中扩展站点模型?

Posted

技术标签:

【中文标题】你如何在 django 中扩展站点模型?【英文标题】:How do you extend the Site model in django? 【发布时间】:2011-02-18 18:51:28 【问题描述】:

在 django 中扩展站点模型的最佳方法是什么?创建一个新模型和 ForeignKey Site 还是有其他方法允许我对 Site 模型进行子类化?

我更喜欢子类化,因为相对而言我更舒服,但我担心它会对内置 Admin 产生影响。

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

这些东西怎么样?他们还工作吗? docs.djangoproject.com/en/dev/ref/contrib/sites/…【参考方案2】:

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

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

在the documentation阅读更多内容。

【讨论】:

我正在寻找添加新字段;但感谢您提供的信息。无论如何都非常有用。【参考方案3】:

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

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

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

【讨论】:

【参考方案4】:

您可以拥有另一个模型,例如 SiteProfile,它与 Site 具有 OneToOne 关系。

【讨论】:

【参考方案5】:

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

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

解决方案

假设您有一个名为 core 的应用。将该应用程序用于以下所有代码,设置文件除外。

    创建一个 SiteProfile 模型,其中包含一个与站点模型具有 OneToOne 关系的站点字段。我还更改了它的 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)

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

如果您只想创建站点配置文件模型,到目前为止我们所做的已经足够了。但是,您会希望在迁移后立即创建第一个配置文件。因为第一个站点是创建的,但不是与其相关的第一个配置文件。如果您不想手动创建,则需要第 3 步。

    在 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),即上述代码的最后一行。

    如果您执行此步骤,您的 SiteProfile 模型将与站点模型完全连接。创建/更新任何 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 步。

    在 settings.py > TEMPLATES > OPTIONS > 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',
            ],
        ,
    ,
]
    使用以下代码在核心应用程序中创建一个 context_processors.py 文件。 需要一个 try-catch 块(catch 部分)以使其更安全。如果您从数据库中删除所有站点,您将在管理页面和前端页面上都出现错误。错误是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')

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

# 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

【讨论】:

【参考方案6】:

显然,您也可以在添加到 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    

【讨论】:

【参考方案7】:

在我看来,最好的方法是使用inheritance编写与站点模型相关的模型

首先,将站点 ID 添加到 Django 设置文件中

SITE_ID = 1

现在在您的一个应用中创建一个模型

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

class Settings(Site):
    field_a = models.CharField(max_length=150, null=True)
    field_b = models.CharField(max_length=150, null=True)

    class Meta:
        verbose_name_plural = 'settings'
        db_table = 'core_settings' # core is name of my app

    def __str__(self) -> str:
        return 'Settings'

然后编辑该应用的apps.py文件

from django.apps import AppConfig
from django.db.models.signals import post_migrate

def build_settings(sender, **kwargs):
    from django.contrib.sites.models import Site
    from .models import Settings
    if Settings.objects.count() < 1:
        Settings.objects.create(site_ptr=Site.objects.first())


class CoreConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'project.apps.core'

    def ready(self) -> None:
        post_migrate.connect(build_settings, sender=self)

现在每次运行迁移时都会在 core_settings 中自动生成一行,与您的站点模型具有一对一的关系

现在您可以像这样访问您的设置


Site.objects.get_current().settings.access_id

可选:如果只有一个站点 从管理站点注销站点模型并禁用在管理站点中删除和创建设置模型

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


admin.site.unregister(Site)

@admin.register(models.Settings)
class SettingAdminModel(admin.ModelAdmin):
    def has_delete_permission(self, request,obj=None) -> bool:
        return False

    def has_add_permission(self, request) -> bool:
        return False

【讨论】:

以上是关于你如何在 django 中扩展站点模型?的主要内容,如果未能解决你的问题,请参考以下文章

django搭建个人博客

Django - 如何在不修改的情况下扩展 3rd 方模型

如何在 django 模型表单中添加自定义字段?

如何在 django admin 中显示多个模型的更改列表?

Django 1.10中文文档-第一个应用Part2-模型和管理站点

如何正确扩展 django 用户模型