I need a nested django admin inline,
which I can include the date field inlines in an other inline like below.
I have the models below:
class Person(models.Model):
name = models.CharField(max_length=200)
id_no = models.IntegerField()
class Certificate(models.Model):
cerfificate_no = models.CharField(max_length=200)
certificate_date = models.DateField(max_length=100)
person = models.ForeignKey(Person)
training = models.CharField(max_length=200)
class Training_Date(models.Model):
date = models.DateField()
certificate = models.ForeignKey(Certificate)
And, the admin below:
class CertificateInline(admin.StackedInline):
model = Certificate
class PersonAdmin(admin.ModelAdmin):
inlines = [CertificateInline,]
admin.site.register(Person,PersonAdmin)
But, I need to include the Training_Date model as inline which is part of Certificate admin inline.
Any idea?
There has been some movement in https://code.djangoproject.com/ticket/9025 recently, but I wouldn't hold my breath.
One common way around this is to link to an admin between first and second (or second and third) level by having both a ModelAdmin and an Inline for the same model:
Give Certificate a ModelAdmin with TrainingDate as an inline. Set show_change_link = True for CertificateInline so you can click on an inline to go to its ModelAdmin change form.
admin.py:
# Certificate change form has training dates as inline
class TrainingDateInline(admin.StackedInline):
model = TrainingDate
class CertificateAdmin(admin.ModelAdmin):
inlines = [TrainingDateInline,]
admin.site.register(Certificate ,CertificateAdmin)
# Person has Certificates inline but rather
# than nesting inlines (not possible), shows a link to
# its own ModelAdmin's change form, for accessing TrainingDates:
class CertificateLinkInline(admin.TabularInline):
model = Certificate
# Whichever fields you want: (I usually use only a couple
# needed to identify the entry)
fields = ('cerfificate_no', 'certificate_date')
# Django 1.8 introduced this, no need to make your own link
show_change_link = True
class PersonAdmin(admin.ModelAdmin):
inlines = [CertificateLinkInline,]
admin.site.register(Person, PersonAdmin)
More universal solution
from django.utils.safestring import mark_safe
from django.urls import reverse
class EditLinkToInlineObject(object):
def edit_link(self, instance):
url = reverse('admin:%s_%s_change' % (
instance._meta.app_label, instance._meta.model_name), args=[instance.pk] )
if instance.pk:
return mark_safe(u'edit'.format(u=url))
else:
return ''
class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
model = MyModel
readonly_fields = ('edit_link', )
class MySecondModelAdmin(admin.ModelAdmin):
inlines = (MyModelInline, )
admin.site.register(MyModel)
admin.site.register(MySecondModel, MySecondModelAdmin)
AFAIK, you can't have a second level of inlines in the default Django admin.
The Django admin is just a normal Django application, so nothing prevents you from implementing a second level of nested forms, but IMHO it would be a kind of convoluted design to implement. Perhaps that is why there is no provision for it.
pip install django-nested-inline
This package should do what you need.
Nested inlines are provided at:
https://github.com/BertrandBordage/django-super-inlines/
pip install django-super-inlines
A more up to date solution (february 2021) is to use the show_change_link config variable: https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link
This does exactly the same as the EditLinkToInlineObject proposed in solutions above, but is less code and is probably well tested by Django Developers
You would just have to define show_change_link=True in each one of your inlines
UPDATE (January 25th, 2022):
Here's the updated link in the docs (Django 4.0): https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link
Use django-nested-admin which is the best package to do nested inlines.
First, install "django-nested-admin":
pip install django-nested-admin
Then, add "nested_admin" to "INSTALLED_APPS" in "settings.py":
# "settings.py"
INSTALLED_APPS = (
# ...
"nested_admin", # Here
)
Then, add "path('_nested_ad..." to "urlpatterns" in "urls.py":
# "urls.py"
from django.urls import include, path
urlpatterns = [
# ...
path('_nested_admin/', include('nested_admin.urls')), # Here
]
Finally, extend "NestedTabularInline" with "Training_DateInline()" and "CertificateInline()" classes and extend "NestedModelAdmin" with "PersonAdmin()" class in "admin.py" as shown below:
# "admin.py"
from .models import Training_Date, Certificate, Person
from nested_admin import NestedTabularInline, NestedModelAdmin
class Training_DateInline(NestedTabularInline):
model = Training_Date
class CertificateInline(NestedTabularInline):
model = Certificate
inlines = [Training_DateInline]
#admin.register(Person)
class PersonAdmin(NestedModelAdmin):
inlines = [CertificateInline]
I used the solution provided by #bigzbig (thank you).
I also wanted to go back to the first list page once changes had been saved so added:
class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
model = MyModel
readonly_fields = ('edit_link', )
def response_post_save_change(self, request, obj):
my_second_model_id = MyModel.objects.get(pk=obj.pk).my_second_model_id
return redirect("/admin/mysite/mysecondmodel/%s/change/" % (my_second_model_id))
Related
Here are my simplified models :
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation)
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Thing(models.Model):
'''
Our 'Thing' class
with a link (generic relationship) to an abstract config
'''
name = models.CharField(
max_length=128, blank=True,
verbose_name=_(u'Name of my thing'))
# Link to our configs
config_content_type = models.ForeignKey(
ContentType,
null=True,
blank=True)
config_object_id = models.PositiveIntegerField(
null=True,
blank=True)
config_object = GenericForeignKey(
'config_content_type',
'config_object_id')
class Config(models.Model):
'''
Base class for custom Configs
'''
class Meta:
abstract = True
name = models.CharField(
max_length=128, blank=True,
verbose_name=_(u'Config Name'))
thing = GenericRelation(
Thing,
related_query_name='config')
class FirstConfig(Config):
pass
class SecondConfig(Config):
pass
And Here's the admin:
from django.contrib import admin
from .models import FirstConfig, SecondConfig, Thing
class FirstConfigInline(admin.StackedInline):
model = FirstConfig
class SecondConfigInline(admin.StackedInline):
model = SecondConfig
class ThingAdmin(admin.ModelAdmin):
model = Thing
def get_inline_instances(self, request, obj=None):
'''
Returns our Thing Config inline
'''
if obj is not None:
m_name = obj.config_object._meta.model_name
if m_name == "firstconfig":
return [FirstConfigInline(self.model, self.admin_site), ]
elif m_name == "secondconfig":
return [SecondConfigInline(self.model, self.admin_site), ]
return []
admin.site.register(Thing, ThingAdmin)
So far, I've a Thing object with a FirstConfig object linked together.
The code is simplified: in an unrelevant part I manage to create my abstract Config at a Thing creation and set the right content_type / object_id.
Now I'd like to see this FirstConfig instance as an inline (FirstConfigInline) in my ThingAdmin.
I tried with the django.contrib.contenttypes.admin.GenericStackedInline, though it does not work with my current models setup.
I tried to play around with the fk_name parameter of my FirstConfigInline.
Also, as you can see, I tried to play around with the 'thing' GenericRelation attribute on my Config Model, without success..
Any idea on how to proceed to correctly setup the admin?
According to the Django Docs you have to define the ct_fk_field and the ct_field if they were changed from the default values. So it may be enough to set ct_field to config_content_type.
Hope it works!
edit: Those values have to be declared in the Inline:
class SecondConfigInline(admin.StackedInline):
model = SecondConfig
ct_fk_field = "config_object_id"
ct_field = "config_content_type"
edit2:
I just realized an error in my assumption. Usually you should declare the Foreignkey on the Inline-model. Depending on the rest of your code you could just remove the generic Foreignkey on Thing+the genericRelation on Config and declare a normal Foreignkey on the Config-Basemodel.
This question is old, but I'll give it a try anyway.
I think the solution depends on what kind of relation you intend to create between Thing and your Config subclasses.
many-to-one/one-to-many
The way it is currently set up, it looks like a many-to-one relation: each Thing points to a single Config subclass, and many Things can point to the same Config subclass. Due to the generic relation, each Thing can point to a different model (not necessarily a Config subclass, unless you do some extra work).
In this case I guess it would make more sense to put the inline on the admin for the Config. That is, create a GenericStackedInline for Thing (which has the GenericForeignkey), and add the inline to a ConfigAdmin, which you can then use for all Config subclasses. Also see the example below. The generic inline will then automatically set the correct content_type and object_id.
many-to-many
On the other hand, if you are looking for a many-to-many relation between Thing and each Config subclass, then I would move the GenericForeignkey into a separate many-to-many table (lets call it ThingConfigRelation).
A bit of code says more than a thousand words, so let's split up your Thing class as follows:
class Thing(models.Model):
name = models.CharField(max_length=128)
class ThingConfigRelation(models.Model):
thing = models.ForeignKey(to=Thing, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, null=True, blank=True,
on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True, blank=True)
config_object = GenericForeignKey(ct_field='content_type',
fk_field='object_id')
Now it does make sense to add an inline to the ThingAdmin. The following is a bare-bones example of an admin that works for both sides of the relation:
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericStackedInline
from .models import Thing, FirstConfig, SecondConfig, ThingConfigRelation
class ConventionalTCRInline(admin.StackedInline):
model = ThingConfigRelation
extra = 0
class GenericTCRInline(GenericStackedInline):
model = ThingConfigRelation
extra = 0
class ThingAdmin(admin.ModelAdmin):
inlines = [ConventionalTCRInline]
class ConfigAdmin(admin.ModelAdmin):
inlines = [GenericTCRInline]
admin.site.register(Thing, ThingAdmin)
admin.site.register(FirstConfig, ConfigAdmin)
admin.site.register(SecondConfig, ConfigAdmin)
Note that we use the conventional inline for the ForeignKey-side of the relation (i.e. in ThingAdmin), and we use the generic inline for the GenericForeignKey-side (in ConfigAdmin).
A tricky bit would be filtering the content_type and object_id fields on the ThingAdmin.
... something completely different:
Another option might be to get rid of the GenericForeignKey altogether and use some kind of single-table inheritance implementation with plain old ForeignKeys instead, a bit like this.
This is my models
from django.db import models
class Page(models.Model):
page_id = models.IntegerField(default=0)
class Question(models.Model):
page = models.ForeignKey(Page)
question = models.CharField(max_length=150)
class Option(models.Model):
question = models.ForeignKey(Question)
option = models.CharField(max_length=100)
image_class = models.CharField(max_length=75)
this is my admin.py
from django.contrib import admin
from .models import Page, Question, Option
class OptionInline(admin.StackedInline):
model = Option
extra = 1
class QuestionInline(admin.StackedInline):
model = Question
extra = 1
inlines = [OptionInline]
class PageAdmin(admin.ModelAdmin):
inlines = [QuestionInline]
admin.site.register(Page, PageAdmin)
basically i want this multi level relation to appear as a multi level inline in the admin site. can someone please help out
Instead of using nested inlines, Django 1.8 provides the InlineModelAdmin.show_change_link
from django.contrib import admin
from .models import Page, Question, Option
class OptionInline(admin.StackedInline):
model = Option
extra = 1
class QuestionInline(admin.StackedInline):
model = Question
extra = 1
show_change_link = True
class PageAdmin(admin.ModelAdmin):
inlines = [QuestionInline,]
admin.site.register(Page, PageAdmin)
class QuestionAdmin((admin.ModelAdmin):
inlines = [OptionInline,]
admin.site.register(Question, QuestionAdmin)
This way, when you save the Page model -having completed the inline Question model- a link called 'change' will appear at the saved instance of the inline Question model. Clicking it, you will land at the main page of the Question model instance with the Option model as inline.
When you complete the Option model inline and hit the 'save and continue editing', the back button should return you to the relevant Page instance.
There is also a post which describes how you can achieve the same result if you use previous Django versions.
Django does not support it out of the box, but there is project called django-nested-inline that will do the job. Also you can make your own solution.
I know this issue has been asked more than once, but as Django is evolving with new version, I'll ask the question again :
I am using the model User (Django User, not in my models.py) and create another model with a Foreign key to User.
models.py :
class Plan(models.Model):
user = models.ForeignKey(User)
I can simply display every Plan in my user by doing this in admin.py :
class PlanInline(admin.TabularInline):
model = Plan
extra = 0
class MyUserAdmin(UserAdmin):
ordering = ('-date_joined', 'username')
inlines = [PlanInline,]
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
But things are about to get more tricky. I want to add a model that has a foreign key pointing to Plan :
class Order(models.Model):
plan = models.ForeignKey('Plan')
And I want to be able to see all Orders for each Plan. As of today, it is impossible to have nested inlines in Django Admin (without editing the HTML, which I want to avoid) :
User
-> Plan 1
-> Order 1
-> Order 2
-> Plan 2
-> Order 3
So my idea is to display in the User Admin only A LINK for each plan, to the page to edit Plans, and put Orders as inline :
class OrderInline(admin.TabularInline):
model = Order
extra = 0
class PlanAdmin(admin.ModelAdmin):
inlines = [OrderInline,]
admin.site.register(Plan, PlanAdmin)
The question is, how do I display a link to a Plan in my User Admin?
class MyUserAdmin(UserAdmin):
ordering = ('-date_joined', 'username')
??? LINK ????
I saw some solutions on this topic : Django InlineModelAdmin: Show partially an inline model and link to the complete model, but they are a bit "dirty' as they make us write HTML and absolute path into the code.
Then I saw this ticket on Djangoproject : https://code.djangoproject.com/ticket/13163. It seems exactly what I'm looking for, and the ticket is "fixed". So I tried adding like in the fix show_change_link = True :
class PlanInline(admin.TabularInline):
model = Plan
extra = 0
show_change_link = True
class MyUserAdmin(UserAdmin):
ordering = ('-date_joined', 'username')
show_change_link = True
inlines = [UserProfileInline, PlanInline]
But it doesn't work (and I have no log or error).
Is there any way to do this in a clean way?
Update for django 1.8
show_change_link = True
https://github.com/django/django/pull/2957/files
I suggest adding a custom PlanInline method that returns the link and see if it helps. Something along these lines:
from django.utils.safestring import mark_safe
from django.core.urlresolvers import reverse
class PlanInline(TabularInline):
model = Plan
readonly_fields = ('change_link',)
...other options here...
def change_link(self, obj):
return mark_safe('Full edit' % \
reverse('admin:myapp_plan_change',
args=(obj.id,)))
Basically all we do here is create the custom method that returns a link to the change page (this specific implementation is not tested, sorry if there is any parse error but you get the idea) and then add it to the readonly_fields as described here: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields
A couple of notes for the change_link method: You need to replace 'myapp' in the view name with your actual application name. The mark_safe method just marks the text as safe for the template engine to render it as html.
I have a model called "Organization" that I've setup as a User profile and I would like to have the fields from the "Organization" model show up on the registration page. How do I go about doing this with django-registration.
# models.py
class Organization(models.Model):
user = models.ForeignKey(User, unique=True)
logo = models.ImageField(upload_to='organizations')
name = models.CharField(max_length=100, null=True, unique=True)
# more fields below etc.
# settings.py
AUTH_PROFILE_MODULE = 'volunteering.organization'
The easiest way to do this would be [tested on django-registration 0.8]:
Somewhere in your project, say forms.py in your organization app
from registration.forms import RegistrationForm
from django.forms import ModelForm
from models import Organization
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
RegistrationForm.base_fields.update(OrganizationForm.base_fields)
class CustomRegistrationForm(RegistrationForm):
def save(self, profile_callback=None):
user = super(CustomRegistrationForm, self).save(profile_callback=None)
org, c = Organization.objects.get_or_create(user=user, \
logo=self.cleaned_data['logo'], \
name=self.cleaned_data['name'])
Then in your root urlconf [but above the regex pattern that includes registration.urls and assuming that regex is r'^accounts/'] add:
from organization.forms import CustomRegistrationForm
urlpatterns += patterns('',
(r'^accounts/register/$', 'registration.views.register', {'form_class':CustomRegistrationForm}),
)
Obviously, you can also create a custom backend, but IMHO this is way easier.
The best way would be to create in the app where you have Organization a file (say, "forms.py"), and do this:
from registration.forms import RegistrationForm
from forms import *
from models import Organization
class RegistrationFormWithOrganization(RegistrationForm):
organization_logo = field.ImageField()
organization_name = field.CharField()
def save(self, profile_callback = None):
Organization.objects.get_or_create(user = self.cleaned_data['user'],
logo = self.cleaned_data['organization_logo'],
name = self.cleaned_data['organization_name'])
super(RegistrationFormWithOrganization, self).save(self, profile_callback)
And then in your base URLs, override the existing URL to registration, and add this form as your the form to use:
form organization.forms import RegistrationFormWithOrganization
url('^/registration/register$', 'registration.views.register',
{'form_class': RegistrationFormWithOrganization}),
url('^/registration/', include('registration.urls')),
Remember that Django will use the first URL that matches the regexp, so will match your call and not django-registration's. It will also tell registration to use your form, not its own. I've skipped a lot of validation here (and, probably, the derivation of the user object... if so, go read the source code to registration to see where it comes from), but this is definitely the right track to get a few things into the page with a minimum amount of effort on your part.
Modify the code as below and try again
urlpatterns += patterns('',
(r'^accounts/register/$', 'registration.views.register', {'form_class':CustomRegistrationForm,'backend': 'registration.backends.default.DefaultBackend'}),
)
"Previously, the form used to collect data during registration was expected to implement a save() method which would create the new user account. This is no longer the case; creating the account is handled by the backend, and so any custom logic should be moved into a custom
backend, or by connecting listeners to the signals sent during the registration process."
Details:
more info can be found here
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?
I prefer subclassing, because relationally I'm more comfortable, but I'm concerned for the impact it will have with the built-in Admin.
I just used my own subclass of Site and created a custom admin for it.
Basically, when you subclass a model in django it creates FK pointing to parent model and allows to access parent model's fields transparently- the same way you'd access parent class attributes in pyhon.
Built in admin won't suffer in any way, but you'll have to un-register Sites ModelAdmin and register your own ModelAdmin.
If you only want to change behaviour of the object, but not add any new fields, you should consider using a "proxy model" (new in Django 1.1). You can add extra Python methods to existing models, and more:
This is what proxy model inheritance is for: creating a proxy for the original model. You can create, delete and update instances of the proxy model and all the data will be saved as if you were using the original (non-proxied) model. The difference is that you can change things like the default model ordering or the default manager in the proxy, without having to alter the original.
Read more in the documentation.
As of Django 2.2 there still no simple straight way to extend Site as can be done for User. Best way to do it now is to create new entity and put parameters there. This is the only way if you want to leverage existing sites support.
class SiteProfile(models.Model):
title = models.TextField()
site = models.OneToOneField(Site, on_delete=models.CASCADE)
You will have to create admin for SiteProfile. Then add some SiteProfile records with linked Site. Now you can use site.siteprofile.title anywhere where you have access to current site from model.
You can have another model like SiteProfile which has a OneToOne relation with Site.
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.
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)
...
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.
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.
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.
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',
],
},
},
]
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
Apparently, you can also create a models.py file in a folder that you add to INSTALLED_APPS, with the following content:
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
In my opinion, the best way to doing this is by writing a model related to the site model using inheritance
First, add the site id to the Django settings file
SITE_ID = 1
now create a model in one of your apps
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'
then edit the apps.py file of that app
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)
now every time you run migrations a row will be auto-generated in core_settings that have a one to one relationship with your Site model
and now you can access your settings like this
Site.objects.get_current().settings.access_id
optional: if have only one site
unregister site model from admin site and disable deleting and creating settings model in admin site
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