Hierarchical data in admin pages in Django - django

In a Django project, I have a hierarchical model using MPTT defined like this in models.py:
class Structure(MPTTModel):
name = models.CharField(max_length=200, unique=True)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
[...]
I'm using FeinCMS to show this hierarchical data in admin pages. I do it like this in admin.py:
class StructureAdmin(tree_editor.TreeEditor):
search_fields = ('name',)
[...]
admin.site.register(Structure, StructureAdmin)
In the admin model page, it works perfectly and the hierarchy can be seen:
It also works when editing or adding:
I have another model in models.py:
class Track(models.Model):
initialStructure = models.ForeignKey(Structure , related_name='track_initialStructure')
finalStructure = models.ForeignKey(Structure, related_name='track_finalStructure')
[...]
However, when adding a new element of this kind, the hierarchy can not be seen:
I've tried to use tree_editor.TreeEditor for the admin view of Track but it gives a lot of errors because Track is not hierarchical, but some of its ForeignKey's are. How could I show the hierarchy when editing an element of model Track?
Thank you very much.

Try changing:
finalStructure = models.ForeignKey(Structure, related_name='track_finalStructure')
to:
finalStructure = TreeForeignKey(Structure, related_name='track_finalStructure')
of course, after importing TreeForeignKey from django-mptt:
from mptt.fields import TreeForeignKey

Related

Trouble displaying inline forms of a ModelAdmin

I am encountering what seems to me a weird bug when rendering Inline forms on the "Add" view of a ModelAdmin.
Here is a minimum example with Django version 2.2.4.
in models.py:
class MyModel(models.Model):
text = models.CharField(max_length=100)
class RelatedModel(models.Model):
parent = models.ForeignKey(MyModel, null=False, on_delete=models.CASCADE)
number = models.DecimalField(decimal_places=2, max_digits=10, null=False, blank=False)
in admin.py:
class RelatedModelInlineTabular(admin.TabularInline):
model = RelatedModel
show_change_link = False
fields = ("number", )
class TestMyModelCreate(admin.ModelAdmin):
fields = ['text', ]
inlines = [RelatedModelInlineTabular]
admin.site.register(MyModel, TestMyModelCreate)
Steps to replicate
Login to django admin website
open the "add" view for MyModel (i.e. navigate to the list of Models and click on the "Add new" button)
Expected result
The form displays an empty text field. Below that, an Inline form is displayed with 3 empty rows for potential related instances of RelatedModel
Actual result
The Inline form is displayed twice, each instance with its own 3 empty rows, as if I had specified it twice.
I attach a screenshot below of the actual page (Discount is the name of the related Model). I tried and I get the same result with both StackedInline and TabularInline.
Am I making some trivial error here that could explain what's happening? Or is this is a known bug? Thank you in advance to anyone that will help.

What is the simplest way to handle M2M through fields in wagtail FieldPanel?

I recently added a "through" model to allow sorting connected objects.
In the example below, a Stage has an ordered list of Blocks linked through StageBlock (with the StageBlock.order field)
#register_snippet
class Block(index.Indexed, models.Model):
title = models.CharField(max_length=100, verbose_name=_("Block Name"))
#register_snippet
class Stage(index.Indexed, models.Model):
title = models.CharField(max_length=100, verbose_name=_("Stage Name"))
blocks = models.ManyToManyField(
to="app.Block",
blank=True,
help_text=_("Blocks associated to this stage"),
related_name="stages",
verbose_name=_("Blocks"),
through="StageBlock",
)
panels = [
FieldPanel("title", classname="title full"),
FieldPanel(
"blocks",
widget=autocomplete.ModelSelect2Multiple(
url="block-autocomplete",
attrs={"data-maximum-selection-length": 3},
),
),
class StageBlock(models.Model):
block = models.ForeignKey("app.Block", on_delete=models.CASCADE)
stage = models.ForeignKey("app.Stage", on_delete=models.CASCADE)
order = models.PositiveSmallIntegerField()
The problem is that the related Wagtail admin form breaks, since it tries to associate Block objects to Stage, without providing the "through" model "order" field value.
I'm wondering what is the cleanest/least effort solution to allow an ordered selection of elements in the admin panel, then to properly save the Stage instance with its blocks and related stageblocks.
For the moment, I will add a custom form to the snippet, and auto-assign the order from the position of blocks in the form data (hoping that it always matches the order of blocks as selected in the fieldpanel).
It feels like this use-case could be auto-handled, either by the wagtail-autocomplete plugin, or by wagtail fieldpanel.
But as far as I understand, fieldpanel will simply re-use the Django ModelMultipleChoiceField field, which returns a html element.
A many-to-many relation with a 'through' model is structurally the same as a one-to-many child relationship on that 'through' model, so one possibility is to implement this with an InlinePanel (as described here):
from django_modelcluster.fields import ParentalKey
from django_modelcluster.models import ClusterableModel
from wagtail.core.models import Orderable
#register_snippet
class Stage(index.Indexed, ClusterableModel):
title = models.CharField(max_length=100, verbose_name=_("Stage Name"))
panels = [
FieldPanel("title", classname="title full"),
InlinePanel("blocks", label="Blocks"),
]
class StageBlock(Orderable):
stage = ParentalKey("app.Stage", on_delete=models.CASCADE, related_name='blocks')
block = models.ForeignKey("app.Block", on_delete=models.CASCADE)
panels = [
FieldPanel('block'),
]

Django One-To-Many Model and Admin inlines

I 'm trying to define 2 models in django like so:
class Selector(models.Model):
# A Beautiful Soup selector
selector = models.CharField(max_length=ELEMENT_SELECTOR_MAX_LENGTH, null=True, blank=True)
def __str__(self):
return self.selector
class Provider(models.Model):
# Articles' parent container selector
articles_parent_container_selector = models.ForeignKey(Selector, related_name="articles_parent_container",
help_text=_("Beautiful Soup selector for all articles' "
"parent container"))
# Article's parent container selector
article_parent_container_selector = models.ForeignKey(Selector, related_name="article_parent_container_selector",
help_text=_("Beautiful Soup selector for each article"))
etc. etc.
The idea is to have more than one selectors for each field of the Provider model.
What I 'm trying to achieve at the admin application, is have charField inlines for each field of the provider model.
So my admin.py is like
from django.contrib import admin
from .models import Provider, Selector
class SelectorInline(admin.StackedInline):
model = Selector
class ProviderAdmin(admin.ModelAdmin):
inlines = [
SelectorInline,
]
admin.site.register(Provider, ProviderAdmin)
I get the error
<class 'news_providers.admin.SelectorInline'>: (admin.E202) 'news_providers.Selector' has no ForeignKey to 'news_providers.Provider'.
I also tried
class SelectorInline(admin.StackedInline):
model = Selector
fk_name = 'articles_parent_container'
as described here: Django inline forms with multiple foreign keys
but the error now is:
<class 'news_providers.admin.SelectorInline'>: (admin.E202) 'news_providers.Selector' has no field named 'articles_parent_container'.
Also tried changing my relation to ManyToMany(which seems more relevant to my use-case as well) and apply the hack found here: http://www.mc706.com/tip_trick_snippets/18/django-manytomany-inline-admin/ , but no luck :/
This should be pretty straight forward, but I 'm afraid django developers didn't take notice of this use case?
Thanks!
So apparently, there is no built-in functionality to display an inline manyToMany model inside another's model page.
The best you can do is define the model like so
models.py
class Selector(models.Model):
# A Beautiful Soup selector
selector = models.CharField(max_length=70, null=True, blank=True)
class Provider(models.Model):
# Articles' parent container selector
articles_parent_container_selector = models.ManyToManyField(Selector, blank=True,
help_text=_("Beautiful Soup selector for all articles' "
"parent container."),
related_name='articles_parent_container')
admin.py
class ArticlesParentContainerSelectorInline(admin.TabularInline):
model = Provider.articles_parent_container_selector.through
verbose_name = "Articles' parent container selector"
class ProviderAdmin(admin.ModelAdmin):
inlines = [
ArticlesParentContainerSelectorInline,
]
exclude = ('articles_parent_container_selector',)
admin.site.register(Provider, ProviderAdmin)
and what you 'll get looks like this:
which is a bit of a disappointment, as I was expecting to get Text Inputs instead of dropdowns (or even both of them), so I could add Selectors without having to click the plus sign...
I 'm leaning towards creating my own widget for the admin application.
Anyway, thanks to everyone who bothered to read!

Display a many-to-many relation in admin without need to edit children

My model is:
class CustomerAccount(models.Model):
name = models.CharField(max_length=50)
class MyUser(AbstractUser):
customer_account = models.ManyToManyField(CustomerAccount, related_name='users', blank=True)
default_customer_account = models.ForeignKey(CustomerAccount, related_name='users_using_default_account', null=True, blank=True)
I want to display in the admin interface of the CustomerAccount this sort of thing:
I don't need to add a MyUser in the CustomerAccount interface.
Most SO questions and docs are related to show an Inline class in the admin, but I don't need it.
How should I do?
The functinality shown above you get by making adding the desired field to the filter_horizontal list (docu).

Many-to-many relationships in Django Admin

models:
class Detail(models.Model):
def __unicode__(self):
return self.title
title = models.CharField(max_length=32)
class Cars(models.Model):
def __unicode__(self):
return self.name
name = models.CharField(max_length=32, unique=True)
details = models.ManyToManyField(Detail)
So, every car has a many details - wheels, engine, etc. How to do this: in Django Admin situated Cars menu, in that menu we have a many lines of details (like in tutorial).
In admin I use:
class DetailInline(admin.TabularInline):
model = Detail
extra = 6
class CarsAdmin(admin.ModelAdmin):
inlines = [DetailInline]
But it has error: Detail has no ForeignKey to Cars. How to fix it?
Django does not natively let you add a reverse inline.
i.e. You can have the Detail page contain an inline admin of all the Cars that contain a ForeignKey to that particular Detail. However, the reverse is not natively possible.
There is a workaround though wherein you have to override the admin template a bit. There is a previous SO question about this here: Inline-like solution for Django Admin where Admin contains ForeignKey to other model