How do you extend the Site model in django?

后端 未结 5 1327
情书的邮戳
情书的邮戳 2021-02-07 23:40

What is the best approach to extending the Site model in django? Creating a new model and ForeignKey the Site or there another approach that allows me to subclass the Site model

5条回答
  •  北荒
    北荒 (楼主)
    2021-02-08 00:15

    It has been a long time since the question was asked, but I think there is not yet (Django 3.1) an easy solution for it like creating a custom user model. In this case, creating a custom user model inheriting from django.contrib.auth.models.AbstractUser model and changing AUTH_USER_MODEL (in settings) to the newly created custom user model solves the issue.

    However, it can be achieved for also Site model with a long solution written below:

    SOLUTION

    Suppose that you have an app with the name core. Use that app for all of the code below, except the settings file.

    1. Create a SiteProfile model with a site field having an OneToOne relation with the Site model. I have also changed its app_label meta so it will be seen under the Sites app in the admin.
    # 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. Register the model in the admin. (in core.admin)

    What we did until now was good enough if you just want to create a site profile model. However, you will want the first profile to be created just after migration. Because the first site is created, but not the first profile related to it. If you don't want to create it by hand, you need the 3rd step.

    1. Write below code in 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
    

    The function (create_default_site_profile) will automatically create the first profile related to the first site after migration, using the post_migrate signal. However, you will need another signal (post_save), the last row of the above code.

    1. If you do this step, your SiteProfile model will have a full connection with the Site model. A SiteProfile object is automatically created/updated when any Site object is created/updated. The signal is called from apps.py with the last row.
    # 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()
    
    
    

    Would you like to use it on templates? e.g. {{ site.name }}

    Then you need the 5th and 6th steps.

    1. Add the below code in 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',
                ],
            },
        },
    ]
    
    1. Create a context_processors.py file in the core app with the code below. A try-catch block is needed (catch part) to make it safer. If you delete all sites from the database you will have an error both in admin and on the front end pages. Error is Site matching query does not exist. So the catch block creates one if it is empty.

    This solution may not be fully qualified if you have a second site and it is deleted. This solution only creates a site with 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')
    

    You can now use the site name, domain, meta_name, long_name, or any field you added, in your templates.

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

    It normally adds two DB queries, one for File.objects and one for FileProfile.objects. However, as it is mentioned in the docs, 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

提交回复
热议问题