Nested inlines in the Django admin? - python

Alright, I have a fairly simple design.
class Update(models.Model):
pub_date = models.DateField()
title = models.CharField(max_length=512)
class Post(models.Model):
update = models.ForeignKey(Update)
body = models.TextField()
order = models.PositiveIntegerField(blank=True)
class Media(models.Model):
post = models.ForeignKey(Post)
thumb = models.ImageField(upload_to='frontpage')
fullImagePath = models.ImageField(upload_to='frontpage')
Is there an easy-ish way to allow a user to create an update all on one page?
What I want is for a user to be able to go to the admin interface, add a new Update, and then while editing an Update add one or more Posts, with each Post having one or more Media items. In addition, I want the user to be able to reorder Posts within an update.
My current attempt has the following in admin.py:
class MediaInline(admin.StackedInline):
model = Media
class PostAdmin(admin.ModelAdmin):
inlines = [MediaInline,]
This let's the user add a new Post item, select the relevant Update, add the Media items to it, and hit save - which is fine. But there's no way to see all the Posts that belong to a given Update in a single place, which in turn means you can't roderder Posts within an update. It's really quite confusing for the end user.
Help?

As of now there is no "built-in" way to have nested inlines (inline inside inline) in django.contrib.admin. Pulling something like this off is possible by having your own ModelAdmin and InlineModelAdmin subclasses that would enable this kind of functionality. See the patches on this ticket http://code.djangoproject.com/ticket/9025 for ideas on how to implement this. You'd also need to provide your own templates that would have nested iteration over both the top level inline and it's child inline.

There is now this egg available, which is a collation of the relevant patches mentioned in the other answer:
https://github.com/theatlantic/django-nested-admin

I have done this using https://github.com/theatlantic/django-nested-admin, for the following Data structure:
Contest
Judges
Contestants
Singers
Songs
My admin.pyfile:
from django.contrib import admin
import nested_admin
from .models import Contest, Contestant, Judge, Song, Singer
class SongInline(nested_admin.NestedTabularInline):
model = Song
extra = 0
class SingerInline(nested_admin.NestedTabularInline):
model = Singer
extra = 0
class ContestantInline(nested_admin.NestedTabularInline):
model = Contestant
inlines = [SongInline, SingerInline]
extra = 0
class JudgeInline(nested_admin.NestedTabularInline):
model = Judge
extra = 0
class ContestAdmin(nested_admin.NestedModelAdmin):
model = Contest
inlines = [ContestantInline, JudgeInline]
extra = 0
admin.site.register(Contest, ContestAdmin)
https://github.com/theatlantic/django-nested-admin appears to be much more actively maintained than the other apps already mentioned (https://github.com/BertrandBordage/django-super-inlines and https://github.com/Soaa-/django-nested-inlines)

I have just ran into this issue as well... Seems this thread which contains the request for the nested inlines feature (https://code.djangoproject.com/ticket/9025#no2) has been updated with further information.
A custom made app called "django-super-inline" has been released. More details here: https://github.com/BertrandBordage/django-super-inlines
Installation and usage instructions below.
Hope this is useful for whomever comes across this.

I ran into a similar issue to this. My approach was to make an UpdateAdmin that held inlines for both Media and Post... it basically just makes it so you have a list of all of the media entries followed by all of the posts in an update.
class MediaInline(admin.StackedInline):
model = Media
class PostInline(admin.StackedInline):
model = Post
class PostAdmin(admin.ModelAdmin):
inlines = [MediaInline,]
class UpdateAdmin(admin.ModelAdmin):
inlines = [MediaInline,PostInline]
It isn't an ideal solution but it works for a quick and dirty work around.

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 "MediaInline()" and "PostInline()" classes and extend "NestedModelAdmin" with "UpdateAdmin()" class in "admin.py" as shown below:
# "admin.py"
from .models import Media, Post, Update
from nested_admin import NestedTabularInline, NestedModelAdmin
class MediaInline(NestedTabularInline):
model = Media
class PostInline(NestedTabularInline):
model = Post
inlines = [MediaInline]
#admin.register(Update)
class UpdateAdmin(NestedModelAdmin):
inlines = [PostInline]

Related

Django/wagtail admin page links pointing to invalid address

I have a wagtail site and have a problem with the ‘users’ section of the admin page
My users/admin.py is :
from django.contrib import admin
from django.contrib.auth import admin as auth_admin
from django.contrib.auth import get_user_model
from psymatik.users.forms import (
UserChangeForm,
UserCreationForm,
)
User = get_user_model()
admin.site.register(User)
class UserAdmin(auth_admin.UserAdmin):
form = UserChangeForm
add_form = UserCreationForm
fieldsets = (
("User", {"fields": ("username",)}),
) + auth_admin.UserAdmin.fieldsets
list_display = ["username", "is_superuser"]
search_fields = ["username"]
And my users/wagtail_hooks.py is:
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from .models import User
class UserAdmin(ModelAdmin):
model = User
menu_label = "Users"
menu_icon = "pick"
menu_order = 200
add_to_settings_menu = False
exclude_from_explorer = False
list_display = ( "name")
list_filter = ("name")
search_fields = ("name")
modeladmin_register(UserAdmin)
The issues I have is that when I am at admin/users and I click on the Users link in the sidebar I am taken to admin/users/user and get the error
“ValueError at /admin/users/user/
Field 'id' expected a number but got 'user’.”
Why is the sidebar link pointing to admin/users/user rather than just admin/users (which does work)? What is the best way to set this up?
When you register a model with ModelAdmin, the URLs will be formed from the app name ('users' here) and the model name ('user'), so /admin/users/user is expected. However, in this case Wagtail already provides a user management area (available from the Settings submenu) that exists under the /admin/users/ URL namespace - these URLs end up colliding with the ones you add through ModelAdmin.
It looks like your UserAdmin definition is more or less duplicating the functionality already provided by Wagtail, so you may not need this at all. If you do, one thing that might work is editing the INSTALLED_APPS setting in your project's settings to move your users app above wagtail.contrib.users - that way, the URL patterns for your own app will take precedence over Wagtail's built in /admin/users/ area, and it will correctly interpret anything under /admin/users/user/ as belonging to your own app (while letting all other URLs under /admin/users/ fall back to the Wagtail built-in area).
If that doesn't work, you'd need to either rename your users app (easier said than done for an established project...) or customise the ModelAdmin setup to use an alternative URL path. It doesn't look like ModelAdmin currently provides an official mechanism to do that, but overriding the AdminURLHelper object ought to work - within users/wagtail_hooks.py:
from wagtail.contrib.modeladmin.helpers import AdminURLHelper
class UsersAdminURLHelper(AdminURLHelper):
def _get_action_url_pattern(self, action):
# hard-code 'user-accounts' as the URL path instead of /users/user/
if action == "index":
return r"^user-accounts/$"
return r"^user-accounts/%s/$" % action
class UserAdmin(ModelAdmin):
model = User
url_helper_class = UsersAdminURLHelper
# all other settings as before
Incidentally, users/admin.py is unrelated here - it controls the Django admin backend, which is distinct from the Wagtail admin.

Django Error admin.E202 'stamm.Workplan' has no ForeignKey to 'enquiry.Costing', Inline-Model in a Many-To-Many Relationship

I'm struggling with the following Problem:
I have created two apps, "stamm" and "enquiry". In the first app "stamm" I have the model Workplan. In the second app "enquiry" I have the model "Costing". I use a M2M relationship via a through-model called "CostingWorkplan" below my Costing model. Then I want to add a TabularInline from the Workplan to my CostingAdmin. When I do this, I get the Error
<class 'enquiry.admin.WorkplanInline'>: (admin.E202) 'stamm.Workplan'
has no ForeignKey to 'enquiry.Costing'.
I checked a couple of threads with a similar Problem, but can't get rid of it. Did I overlook something?
Here is my Python Code:
# stamm.models.py
class Workplan(model.Model):
some_fields = ...
# enquiry.models.py
from stamm.models import Workplan
class Costing(model.Model):
some_fields = ...
costing_workplan = models.ManyToManyField(Workplan, through='CostingWorkplan')
class CostingWorkplan(models.Model):
workplan = models.ForeignKey(Workplan, on_delete=models.RESTRICT)
costing = models.ForeignKey(Costing, on_delete=models.RESTRICT)
# enquiry.admin.py
from .models import Costing
from stamm.models import Workplan
class WorkplanInline(admin.TabularInline):
model = Workplan
#admin.register(Costing)
class CostingAdmin(admin.ModelAdmin):
inlines = (WorkplanInline, )
After hours of research, reading and testing yesterday, I found the answer.
I overread some important detail in the documentation for Inline models working with many-to-many models.
The important key is to replace
class WorkplanInline(admin.TabularInline):
model = Workplan
with
class WorkplanInline(admin.TabularInline):
model = Costing.costing_workplan.through
You can read more about it in the ModelAdmin Documentation under the point "Working with many-to-many models":
(https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin)
Workplan and Costing can also inline CostingWorkplan as shown below;
class CostingWorkplanInline(admin.TabularInline):
model = CostingWorkplan
#admin.register(Workplan)
class WorkplanAdmin(admin.ModelAdmin):
inlines = (CostingWorkplanInline,)
#admin.register(Costing)
class CostingAdmin(admin.ModelAdmin):
inlines = (CostingWorkplanInline,)

Manager isn't available while setting up django-fmc

I'm trying to get django-fmc set up with Django (v 1.97, Python v2.7.12, djangorestframework v3.3.3) to handle storing registration ids and sending notifications to devices. I am following the tutorial they provide but it doesn't seem to be working.
I am getting the following error when running my local server and python manage.py fcm_urls:
...
File "C:\Work\Dev\LiveTracking\Api\app\views.py", line 50, in DeviceViewSet
queryset = Device.objects.all()
File "C:\Work\Dev\LiveTracking\Api\env\lib\site-packages\django\db\models\manager.py", line 277, in __get__
self.model._meta.swapped,
AttributeError: Manager isn't available; 'fcm.Device' has been swapped for 'app.MyDevice'
I don't want to add additional fields to the MyDevice model for now. I've looked all over but can't fix this error. If anyone can shed some insight into this error it would be much appreciated.
Here are some of my code snippets:
settings.py
INSTALLED_APPS = (
'fcm',
)
# Firebase Cloud Messaging Key
FCM_APIKEY = 'AIzaSyCaqHZIcaGDOpfTZUmAHEowsqD-fCtow6A'
# Location of device model
FCM_DEVICE_MODEL = 'app.MyDevice'
serializers.py
from fcm.models import Device
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ('dev_id','reg_id','name','is_active')
views.py
from rest_framework import viewsets
from fcm.models import Device
from fcm.serializers import DeviceSerializer
class DeviceViewSet(viewsets.ModelViewSet):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
urls.py
from rest_framework import routers
from fcm.views import DeviceViewSet
router = routers.DefaultRouter()
router.register(r'devices', DeviceViewSet)
urlpatterns = [
url(r'^v1/', include(router.urls)),
]
swappable is an undocumented feature, actually only supposed to be used for custom User models. The doc on custom user models clearly states that once you use a custom user model, directly referencing contrib.auth.models.User won't work:
If you reference User directly (for example, by referring to it in a foreign key), your code will not work in projects where the AUTH_USER_MODEL setting has been changed to a different user model.
You probably want to read the rest of this chapter FWIW.
To make a long story short: as Daniel Roseman mentions, you very probably want to use your own MyDevice model instead of the default Device one. And eventually contribute back a patch to django-fcm doc if it solves the issue.

Why GenericRelation fields does not work in Data Migrations(Django)

I want to make data migration in order to add user read post in database. There is such code:
def user_read_posts(apps, schema_editor):
User = apps.get_model("main", "User")
Post = apps.get_model("main", "Post")
Comment = apps.get_model("comments", "Comment")
comments = Comment.objects.all()
for comment in comments:
print (comment.content_object.__class__.__name__)
if isinstance(comment.content_object, Post):
comment.user.read_posts.add(comment.content_object)
class Migration(migrations.Migration):
dependencies = [
('main', '0039_auto_20160314_0906'),
]
operations = [
migrations.RunPython(user_read_posts),
]
And in line print (comment.content_object.__class__.__name__) django raise error:
AttributeError: 'Comment' object has no attribute 'content_object'
Comment model:
class GuidaComment(GenericRelationModel):
user = models.ForeignKey(GuidaUser)
text = models.TextField()
So what should I do?
Thanks.
In RunPython migration scripts apps.get_models() will get historical version of models, not the latest django models you have in source. These historical models are quite limited. Excerpt from django documentation:
historical models will not have any custom methods that you have
defined. They will, however, have the same fields, relationships,
managers (limited to those with use_in_migrations = True) and Meta
options (also versioned, so they may be different from your current
ones).
But that does not mean that you can't use latest and fully functional models by just importing them and using them. In that case you are risking that the migration script won't run correctly in the future - since latest version of model can change (e.g. model method can be renamed, deleted, logic changed etc.). So, it is possible but you need to be aware of the risk using models like this.
Content types framework is pretty old and rarely changed django contrib application and IMHO it is safe enough to use it like this.
Inspired by the answer and solution I made and use, I have made following draft I hope you will find useful:
def user_read_posts(apps, schema_editor):
User = apps.get_model("main", "User")
Post = apps.get_model("main", "Post")
Comment = apps.get_model("comments", "Comment")
from django.contrib.contenttypes.models import ContentType
comments = Comment.objects.all()
for comment in comments:
try:
# assuming that Comment has object_id field holding reference to PK
# of the referenced object
ct = ContentType.objects.get(model=comment.content_type.model,
app_label=comment.content_type.app_label)
content_object = ct.get_object_for_this_type(pk=comment.object_id)
except Exception, ex:
# TODO: can happen if some content type / model is deleted.
continue
print (content_object.__class__.__name__)
if isinstance(content_object, Post):
# TODO: maybe you will need to copy/adapt read_posts.add method's logic here
comment.user.read_posts.add(content_object)
Instead of importing ContentType as suggested in the other answer, I would use apps.get_model to get the historic model, as mentioned here.
Here's what that would look like for the OP's example:
def user_read_posts(apps, schema_editor):
User = apps.get_model("main", "User")
Post = apps.get_model("main", "Post")
Comment = apps.get_model("comments", "Comment")
for comment in Comment.objects.all():
# get historic model for the specified content_type
model = apps.get_model(
app_label=comment.content_type.app_label,
model_name=comment.content_type.model,
)
# get the object with the specified id
content_object = model.objects.get(id=comment.object_id)
if isinstance(content_object, Post):
comment.user.read_posts.add(content_object)
...
This assumes Comment has content_type and object_id fields for the generic relation, as in the generic-relations example.
Exception handling has been omitted for clarity.

django - add existing ModelAdmin with his inlines to another admin form

i have:
class BookAdmin(ModelAdmin):
inlines = [ TextInline,]
class EventAdmin(ModelAdmin):
pass
when viewing Event at admin, i want that BookAdmin will be shown at the same form(with his inlines)
is it possible?
thanks
If the point is just to show data from Book you may add Book fields in EventAdmin with __ notation (if the two models have some kind of relation) or just define EventAdmin methods that fetches values from Book and add them as readonly_fields.
Something like this:
class EventAdmin(ModelAdmin):
def book_texts(self, instance):
out = ''
for book in instance.books:
for inline in book.your_other_replated_class:
out += inline.value_to_print
return out
book_text.allow_tags = True
readonly_fields = [book_texts]
Otherwise, if the point is to be able to submit the two forms together, I'll suggest to define a custom Form class and handle the submitted data in a View.

Categories

Resources