I have a model Item that has a foreign key pointing to Category.
From the Category admin page, I would like to be able to choose existing Item objects and modify them too.
class Item(models.Model):
name = models.CharField(max_length=63)
category = models.ForeignKey('Category', null=True, related_name="items")
class Category(models.Model):
name = models.CharField(max_length=63)
I have tried setting up the admin this way but it's simply displaying blank inlines and no magnifying glass or select dropdown to choose from existing Item instances.
class ItemInline(admin.StackedInline):
model = Item
allow_add = True
raw_id_fields = ('category',)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name']
inlines = [
ItemInline
]
# also tried putting raw_id_fields = ('items',) here
# but it prompts an error saying 'CategoryAdmin.raw_id_fields' refers to field 'items' that is missing from model 'Category'.
It seems you've misunderstood how the link works. Django doesn't offer a selection for reverse foreign keys. From the Item admin you could select the Category like that. But not the other way around.
One workaround would be to use a project that adds custom widgets such as Django Tags Input which adds a tag-like input field to your admin.
In this case the configuration would look something like this:
settings.py
INSTALLED_APPS = (
# ... your other installed apps
'tags_input',
)
TAGS_INPUT_MAPPINGS = {
'your_app.Item': {
'field': 'name',
},
}
admin.py
from tags_input import admin as tags_input_admin
class CategoryAdmin(tags_input_admin.TagsInputAdmin):
list_display = ['name']
urls.py
from django.conf import urls
urlpatterns = patterns('',
url(r'^tags_input/', include('tags_input.urls', namespace='tags_input')),
# ... other urls ...
)
PS: To easily create a fully functioning Django admin config try the Django Admin Generator package.
Disclaimer: The linked projects are ones I wrote.
Related
My Profile model has a OneToOne relation with Django's built-in User model.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
verified = models.BooleanField(default=False)
If I want to change user's password or properties like Active or Superuser I have to do it in one Change User page, and to edit verified property I have to go to another.
Is there any way to merge this:
And this:
Into one form so I can edit everything about a user in one page?
Edit 1:
As you guys suggested the StackedInline approach, let's see how it turns out.
Please first look at Django's default Admin site (first screenshot above):
Everything is grouped in sections and sections have titles.
Look at how the password information is displayed.
There's a link to change the password.
Now I implement the StackedInline solution.
Please note that this is in the admin.py of my myapp:
from django.contrib import admin
from .models import Profile
from django.contrib.auth.models import User
# Register your models here.
class ProfileInline(admin.StackedInline):
model = Profile
class UserAdmin(admin.ModelAdmin):
inlines = (ProfileInline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Now let's look at Admin site:
Everything is scattered. Sections and their titles are gone (Personal info, Permissions, etc).
Password field shows the hashed password. All other information is gone.
There's no link to change the password.
Edit 2:
To solve the problem of Edit 1 I look at the source code of Django (https://github.com/django/django/blob/main/django/contrib/auth/admin.py) and add update my code as below:
class UserAdmin(admin.ModelAdmin):
inlines = (ProfileInline, )
fieldsets = (
(None, {"fields": ("username", "password")}),
(("Personal info"), {"fields": ("first_name", "last_name", "email")}),
(
("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
),
},
),
(("Important dates"), {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("username", "password1", "password2"),
},
),
)
filter_horizontal = (
"groups",
"user_permissions",
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Now I have two sections in the Admin site:
The section on the top shows almost everything (except that the password field is still different and there's no link to change the password and also the verified field is not there) but sections and titles are back.
Then there's this additional and completely unnecessary part:
As you can see:
All fields of information about the user is repeated
Look at the password field
Information is not grouped in sections with titles
verified filed appears.
OP can use one of the InlineModelAdmin objects such as StackedInline. This allows one to create inline forms providing one the ability to edit models on the same page as a parent model.
Adapting for OP's case, it would be something like
from django.contrib import admin
class ProfileInline(admin.StackedInline):
model = Profile
class UserAdmin(admin.ModelAdmin):
inlines = [
ProfileInline,
]
admin.site.register(User, UserAdmin)
Now OP's admin site is set up to edit Profile objects inline from the User detail page.
User model Extension vs Inheritance.
Your profile model only add some elements to user. In this case Model inheritance can be better.
# models.py
class Profile(user):
verified = models.BooleanField(default=False)
after that you can achieve all fields for user and for profile:
# admin.py
class ProfileAdmin(ModelAdmin):
fields = '__all__'
if you don't want to switch on Model inheritance.
You can use InlineModel in UserAdmin to change related model.
# admin.py
class ProfileInline(StackedInline):
model=Profile
class UserAdmin(ModelAdmin):
inlines = (ProfileInline, )
you can use override AbstractUser from model Django so you can merge user in one place like this:
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
verified = models.BooleanField(default=False)
and you need to add this line to Django settings.py file so that Django knows to use the new User class:
AUTH_USER_MODEL = 'users.CustomUser'
then make sure to migrate :
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
The goal of this application is to let users select their favourite fonts using checkboxes and use the selected fonts somewhere else.
The thing is that, all the fonts are entered through the Admin panel by the admin, so the form should be able to get it from there.
This is what I thought doing :
forms.py
class ContactForm1(forms.ModelForm):
choice = forms.MultipleChoiceField(choices=""" Get my models title as choice """, widget=forms.CheckboxSelectMultiple())
class Meta:
model = ImageCheckView
fields = ['title', 'choice']
...
models.py
class ImageCheckView(models.Model):
title = models.CharField(max_length=100, unique=True)
...
What it will have to look like :
(I want to keep the selected options so I can use it somewhere else.)
How can I achieve the following ?
Found a solution, I can create a new checkbox field on my forms and pass the object choices like this :
checkbox = forms.MultipleChoiceField(ImageCheckView.objects.all(), widget=forms.CheckboxSelectMultiple)
The Django admin panel has a search autocomplete with search_fields on the list of objects of a model, but right now I have 3000 users. To add a user manually is dificult with the selectbox; I need the same behavior like the searchfields for selecting a user foreinkey User.
How can I include the Django search feature on the form for editing inside the admin panel?
from myapp.models import Red
from django.contrib.auth.models import User
class Red(models.Model):
customer = models.ForeignKey(User, verbose_name="Cliente")
pub_date = models.DateTimeField(default=datetime.now, blank=True)
Since Django 2.0, you can use autocomplete_fields to generate autocomplete fields for foreign keys.
class UserAdmin(admin.ModelAdmin):
search_fields = ['username', 'email']
class RedAdmin(admin.ModelAdmin):
autocomplete_fields = ['customer']
Django has no built-in autocomplete functionality for foreign keys on admin but the raw_id_fields option may help:
class RedAdmin(admin.ModelAdmin):
raw_id_fields = ("customer", )
If you want real autocomplete then you have to use 3rd-party app like django-autocomplete-light or some of the other solutions.
My Django project structure:
mysite/
mysite/
...
urls.py
scradeweb/
...
models.py
serializers.py
views.py
urls.py
manage.py
If I use Django REST router in the project level urls.py (mysite/urls.py) like below, everything works fine:
# mysite/urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from .settings import USER_CREATED_APPS
from rest_framework.routers import DefaultRouter
from scradeweb import views
router = DefaultRouter()
router.register(r'threads', views.ThreadViewSet, )
router.register(r'posts', views.PostViewSet)
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'scradeweb/', include('scradeweb.urls', namespace='scradeweb')),
url(r'^', include(router.urls)),
)
I like keeping all my application (scradeweb) related code within its
directory, so I move router to scradeweb/urls.py:
# scradeweb/urls.py
from django.conf.urls import url, patterns, include
from scradeweb import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'threads', views.ThreadViewSet, )
router.register(r'posts', views.PostViewSet)
urlpatterns = router.urls
When I go to http://127.0.0.1:8000/scradeweb/posts/, it raises the exception:
ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "thread-detail".
You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
Why does it not work?
Here is my scradeweb/models.py
class Thread(models.Model):
thread_id = models.IntegerField()
sticky = models.NullBooleanField()
prefix = models.CharField(max_length=255)
title = models.CharField(max_length=255)
start_time = models.DateTimeField()
author = models.CharField(max_length=255)
url = models.URLField(unique=True)
forum = models.ForeignKey(Forum, related_name='threads')
class Meta:
ordering = ('start_time', )
class Post(models.Model):
post_id = models.IntegerField()
url = models.URLField(unique=True)
post_number = models.IntegerField()
start_time = models.DateTimeField(blank=True)
last_edited_time = models.DateTimeField(blank=True, null=True)
author = models.CharField(max_length=255, blank=True)
content = models.TextField(blank=True)
thread = models.ForeignKey(Thread, related_name='posts')
class Meta:
ordering = ('post_number', )
scradeweb/serializers.py:
from rest_framework import serializers
from scradeweb.models import Thread, Post
class ThreadSerializer(serializers.HyperlinkedModelSerializer):
posts = \
serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='post-detail',
)
class Meta:
model = Thread
fields = ('pk', 'thread_id', 'title', 'url', 'posts')
read_only_fields = ('thread_id', )
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
scradeweb/views.py:
...
class ThreadViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Thread.objects.all()
serializer_class = ThreadSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
The problem here is that you are using Django REST Framework with a namespace. Many components do not work well with them, which doesn't mean they can't be used, so you need to work around the issues by doing a lot of manual work. The main problem is with hyperlinked relations (Hyperlinked*Fields), because they have to reverse views, and that requires that the namespace is specified.
The first place to start at is the router level, which doesn't currently support namespaces. This is only an issue for the DefaultRouter, as it will build out the index page that contains a list of reversed urls, which will trigger errors right away. This has been fixed in the upcoming release, but for now you're stuck without it. At best (DRF 3.0+), the index page will be completely empty (or point to incorrect urls), in the worst case scenario (DRF 2.x) it will always trigger an internal server error.
The second place to look at is the serializer fields. By default, the HyperlinkedModelSerializer will automatically generate HyperlinkedRelatedField and HyperlinkedIdentityField fields with non-namespaced urls. When you are using namespaces, you have to override all of these automatically generated fields. This generally means you are better off just not using a HyperlinkedModelSerializer, and instead controlling the output with a ModelSerializer.
The first (and easiest) thing to fix is the url field that is automatically generated for all objects. This is automatically generated with view_name set to the detail page (thread-detail in this case), without a namespace. You are going to need to override the view_name and include the namespace in front of it (so scradeweb:thread-detail in this case).
class ThreadSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="scradeweb:thread-detail",
)
You are also going to need to override all of the automatically generated related fields. This is slightly more difficult for writable fields, but I would always recommend looking at repr(MySerializer()) in those cases to see the arguments for the automatically generated fields. For the case of read-only fields, it's a matter of just copying over the default arguments and changing the view_name again.
class ThreadSerializer(serializers.ModelSerializer):
posts = serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='scradeweb:post-detail',
)
This will need to be done for all fields, and it doesn't look like Django REST Framework will be adding the ability to do it automatically in the near future. While this will not be enjoyable at first, it will give you a great opportunity to appreciate everything DRF previously did for you automatically.
I have had the same kind of issue and fix it just change the "HyperlinkedModelSerializer" to "ModelSerializer".
I am just following the rest framework tutorial and got stucked in this part
class TodoSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TodoList
fields = '__all__'
to:
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = TodoList
fields = '__all__'
I have three models - two of them are in one app, and the third one is on the another. The structure is like this:
taapp.models:
class Teachers(model.Model):
fullname = models.CharField(max_length=50)
...
class TeachersScale(model.Model):
teacher = models.ForeignKey("Teachers")
abbr = models.ForeignKey("questions.QuestionTypes")
questions.models:
class QuestionTypes(models.Model):
abbr = models.CharField(max_length=5)
......
I registered all these models to admin:
taapp.admin:
from taapp.models import Teachers
from taapp.models import TeachersScale
from django.contrib import admin
from admin_forms import TeachersAdmin, TeachersScaleAdmin
admin.site.register(Teachers, TeachersAdmin)
admin.site.register(TeachersScale, TeachersScaleAdmin)
taapp.admin_forms:
from django import forms
from django.contrib import admin
class TeachersAdmin(admin.ModelAdmin):
list_display = ('fullname', 'email', 'registration_date')
class TeachersScaleAdmin(admin.ModelAdmin):
list_display = ('teacher', 'abbr')
list_filter = ['teacher','abbr']
When I try to add a field to TeachersScale in admin site, I get the following error:
DatabaseError at /admin/taapp/teachersscale/add/
(1146, "Table 'taapp.questions_questiontypes' doesn't exist")
It treats QuestionTypes, as it is a model in taapp. How to solve it? Or is there something wrong with my db design?
I tried TabularInline for QuestionTypes to see if reverse adding works. Well, it works:
questions.admin:
class TeachersScaleInline(admin.TabularInline):
model = TeachersScale
class QuestionTypesAdmin(admin.ModelAdmin):
inlines = [TeachersScaleInline]
Thanks in advance.
It looks like you haven't actually created your questions table, or if you have you've forced it into a different database. Foreign keys expect to share the same database, and it's perfectly standard to have multiple apps sharing the same database. That's why the app name is part of the automatically generated table name.