Displaying ForeignKey data in Django admin change/add page - python

I'm trying to get an attribute of a model to show up in the Django admin change/add page of another model. Here are my models:
class Download(model.Model):
task = models.ForeignKey('Task')
class Task(model.Model):
added_at = models.DateTimeField(...)
Can't switch the foreignkey around, so I can't use Inlines, and of course fields = ('task__added_at',) doesn't work here either.
What's the standard approach to something like this? (or am I stretching the Admin too far?)
I'm already using a custom template, so if that's the answer that can be done. However, I'd prefer to do this at the admin level.

If you don't need to edit it, you can display it as a readonly field:
class DownloadAdmin(admin.ModelAdmin):
readonly_fields = ('task_added_at',)
def task_added_at(self, obj):
return obj.task.added_at

Related

Make boolean values editable in list_display Wagtail?

I want to edit boolean values from the list page in Wagtail Admin. I cant seam to get this functionality working in Wagtail as its built upon Django i believe it can work on Wagtail as well but i cant figure it out.
Django way of doing it:
class TaskAdmin(models.ModelAdmin):
list_display = (..., 'boolean_field')
list_editable = ('boolean_field',)
Thank you
There is a way to add site settings to the Wagtail admin. Is this what you're looking for? You can make boolean toggle switches in the settings menu and then pass the values to a view or a template. It would look something like this:
from django.db import models
from wagtail.contrib.settings.models import BaseSetting, register_setting
#register_setting
class TaskAdmin(BaseSetting):
list_display = models.BooleanField()
list_editable = models.BooleanField()
Docs are here: https://docs.wagtail.io/en/stable/reference/contrib/settings.html

Limiting choices in foreign key dropdown in Django using Generic Views ( CreateView )

I've two models:
First one:
class A(models.Model):
a_user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
a_title = models.CharField("A title", max_length=500)
Second one:
class B(models.Model):
b_a = models.ForeignKey(A, verbose_name=('A'), unique=False, on_delete=models.CASCADE)
b_details = models.TextField()
Now, I'm using CreateView to create form for Value filling :
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
Then using this to render these field in templates.
Now, my problem is, while giving the field b_a ( which is the dropdown ), it list downs all the values of model A, but the need is to list only the values of model A which belongs to the particular logged in user, in the dropdown.
I've seen all the answers, but still not able to solve the problem.
The things I've tried:
limit_choices_to in models : Not able to pass the value of A in the limit_choices
form_valid : Don't have the model A in the CreateView, as only B is reffered model in B_Create
passing primary key of A in templates via url : Then there is no instance of A in the template so can't access. Also, don't want to handle it in templates.
I'm new to Django and still learning, so don't know to override admin form.
Please suggest the implemented way, if possible to the problem. I've researched and tried most of the similar questions with no result for my particular problem. I feel like, this is a dumb question to ask, but I'm stuck here, so need help.
Thanks..
(Please feel free to suggest corrections.)
You have access to self.request.user in the form_valid of the view. But in order to limit the choices in the form you have to customize the form before it is served initially. You best override the view's get_form and set the form field's queryset:
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
def get_form(self, *args, **kwargs):
form = super(B_Create, self).get_form(*args, **kwargs)
form.fields['b_a'].queryset = self.request.user.a_set.all()
# form.fields['b_a'].queryset = A.objects.filter(a_user=self.request.user)
return form
Generally, there are three places where you can influence the choices of a ModelChoiceField:
If the choices need no runtime knowledge of your data, user, or form instance, and are the same in every context where a modelform might be used, you can set limit_choices_to on the ForeignKey field itself; as module level code, this is evaluated once at module import time. The according query will be built and executed every time a form is rendered.
If the choices need no runtime knowledge, but might be different in different forms, you can use custom ModelForms and set the queryset in the field definition of the respective form field.
If the queryset needs any runtime information, you can either override the __init__ of a custom form and pass it any information it needs to set the field's queryset or you just modify the queryset on the form after it is created which often is a quicker fix and django's default views provide nice hooks to do that (see the code above).
The #schwobaseggl answer is excellent.
Here is a Python 3 version. I needed to limit the projects dropdown input based on the logged-in user.
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
template_name = 'brand/product-create.html'
fields = '__all__'
def get_form(self, form_class=None):
form = super().get_form(form_class=None)
form.fields['project'].queryset = form.fields['project'].queryset.filter(owner_id=self.request.user.id)
return form

Django's Inline Admin: a 'pre-filled' field

I'm working on my first Django project, in which I want a user to be able to create custom forms, in the admin, and add fields to it as he or she needs them. For this, I've added a reusable app to my project, found on github at:
https://github.com/stephenmcd/django-forms-builder
I'm having trouble, because I want to make it so 1 specific field is 'default' for every form that's ever created, because it will be necessary in every situation (by the way my, it's irrelevant here, but this field corresponds to a point on the map).
An important section of code from this django-forms-builder app, showing the use of admin.TabularInline:
#admin.py
#...
class FieldAdmin(admin.TabularInline):
model = Field
exclude = ('slug', )
class FormAdmin(admin.ModelAdmin):
formentry_model = FormEntry
fieldentry_model = FieldEntry
inlines = (FieldAdmin,)
#...
So my question is: is there any simple way to add default (already filled fields) for an admin 'TabularInline' in the recent Django versions? If not possible, I would really appreciate a pointer to somewhere I could learn how to go about solving this.
Important: I've searched for similar questions here and even googled the issue. I've found some old questions (all from 2014 or way older) that mention this possibility not being directly provided by Django. The answers involved somewhat complex/confusing suggestions, given that I'm a Django begginer.
There are a couple of ways to achieve this.
1: Set a default on the field in the model.
Django's dynamic admin forms and inlines are smart enough to detect this and display it automatically as a default choice or entry.
class Book(models.Model):
rating = models.IntegerField(default=0)
2: Use a custom form in your TabularInline class.
class BookInlineForm(models.ModelForm):
class Meta:
model = Book
fields = ('rating', )
def __init__(self, *args, **kwargs):
initial = kwargs.pop('initial', {})
# add a default rating if one hasn't been passed in
initial['rating'] = initial.get('rating', 0)
kwargs['initial'] = initial
super(BookInlineForm, self).__init__(
*args, **kwargs
)
class BookTabularInline(admin.TabularInline):
model = Book
form = BookInlineForm
class ShelfAdmin(admin.ModelAdmin):
inlines = (BookAdmin,)

Is it possible to have an inline formset built into a ModelForm or Form in django?

Given the following example models:
class Reporter(models.Model):
pass
class Article(models.Model):
reporter = models.ForeignKey(Reporter)
I want to define a ReporterForm that would allow you to add/edit articles for that reporter and also to edit the reporter's own fields. (non-existent in the example code)
I want to be able to use ReporterForm in the django admin panel so that whenever a reporter is edited, the admin can also see the articles that belong to that reporter inline.
Is there a clean way to do this? Or is this not the right model design to start with?
PS. I considered giving Reporter a ManyToManyField(Article) and just letting django do its magic but that implies that Articles can belong to many different Reporters, doesn't it?
This is exactly what inline formsets are for. You can have inline model formsets:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#inline-formsets
To do this in the admin site there is InlineModelAdmin:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#inlinemodeladmin-objects
from django.contrib import admin
class ArticleInline(admin.TabularInline):
model = Article
class ReporterAdmin(admin.ModelAdmin):
inlines = [
ArticleInline,
]

django-admin - how to modify ModelAdmin to create multiple objects at once?

let's assume that I have very basic model
class Message(models.Model):
msg = models.CharField(max_length=30)
this model is registered with admin module:
class MessageAdmin(admin.ModelAdmin):
pass
admin.site.register(Message, MessageAdmin)
Currently when I go into the admin interface, after clicking "Add message" I have only one form where I can enter the msg.
I would like to have multiple forms (formset perhaps) on the "Add page" so I can create multiple messages at once. It's really annoying having to click "Save and add another" every single time.
Ideally I would like to achieve something like InlineModelAdmin but it turns out that you can use it only for the models that are related to the object which is edited.
What would you recommend to use to resolve this problem?
This may not be exactly what you are looking for, but if you want to create multiple objects at the same time you could to somehthing like this:
#In /forms.py
MessageAdminForm(forms.ModelForm):
msg = CharField(max_length=30)
count = IntegerField()
#In /admin.py
from app.admin import MessageAdminForm
MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
fieldsets = (
(None, {
'fields' : ('msg','count')
}),)
def save_model(self, request, obj, form, change):
obj.msg = form.cleaned_data['msg']
obj.save()
for messages in range(form.cleaned_data['count']):
message = Message(msg=form.cleaned_data['msg'])
message.save()
Basicly what you are doing is creating a custom form for your admin template, which ask the user how many times the object shall be created. The logic is than interpreted in the save_model method.
As a workaround, Since, It is likely that you have a FK to User, so you could define an InlineModel on the User model.
Otherwise, the easiest approach may be to create a custom admin view since, there isn't a generic admin view that displays and saves formsets.
This is easy if you are using an Inline. Then you could use extra = 10 or however many extra formsets you want. There doesn't seem to be an equivalent for the ModelAdmin.
Of course in your messages model you would need to create a ForeignKey to some sort of message grouping model as another layer of function and to get the multi-formset layout that you are looking for.
For example:
models.py:
class Group(models.Model):
name = models.CharField(max_length=30)
class Message(models.Model):
msg = models.CharField(max_length=30)
grp = models.ForeignKey(Group)
admin.py:
class MessageInline(admin.TabularInline):
model = Message
extra = 10
class GroupAdmin(admin.ModelAdmin):
inlines = [MessageInline]
admin.site.register(Group, GroupAdmin)
This would give you what you want in the Admin view and create grouping (even if you only allow for one group) and the only extra field would be the name in the group model. I am not even sure you would need that. Also I am sure the value for extra could be generated dynamically for an arbitrary value.
I hope this helps!

Categories