django inline admin: dynamically delete non-model fields from model form - django

When I display a user profile inline in the admin, I want to delete some non-model fields based on certain conditions.
class ProfileInline(admin.StackedInline):
model = Profile
form = ProfileForm
max_num = 1
can_delete = False
The ProfileForm has some additional fields, which are not related to the model and I want to delete them under certain conditions, e.g. a certain user is editing the form.
I tried to explicitly set the fields to include and I also tried to exclude the fields with the help of get_formset():
def get_formset(self, request, obj=None, **kwargs):
kwargs['fields'] = ['fieldX', 'fieldY']
#kwargs['exclude'] = ['fieldZ']
return super(ProfileInline, self).get_formset(request, obj, **kwargs)
It works fine with model fields, but the non-model fields are always shown.

This is an old topic, but I think I can help.
You can hide the fields using the fieldsets property of the ModelAdmin class.
Just lists the fields that you want to show.

Related

Django createview custom validation

I am using createview for creating few fields of a model. I want to provide custom validation to attributes in my form. I am not sure how to do it through the CreateView. I don't want to create a Modelform for it.
Generally custom validation for the attributes is performed by clean_attr() method in forms. So, Is there any way to perform this in createview ?
my createview class
#method_decorator(never_cache, name='dispatch')
class AppCreateView(LoginRequiredMixin, CreateView):
model = models.App
fields = ['name', 'background', 'font', 'textcolor']
def get_context_data(self, *args, **kwargs):
context = super(AppCreateView, self).get_context_data(*args, **kwargs)
context['view'] = 'create'
return context
In the fields, I am excluding a fild called "date" (which has to be today). Is there any way to set the date attribute in CreateView ?
Thanks
update
My questions
How to make custom validation in AppCreateView ?
How to fill the other attributes apart of the user filled ones such as date ?
If you want to do that you can add validators on your model which will be catched by CreateView : https://docs.djangoproject.com/en/2.1/ref/validators/
For the date, juste add on your field :
my_field = models.DateTimeField(auto_now=True) #the date will be timezone.now() when you save the instance.

'UpdateView with a ModelForm as form_class'-issue

The code I would like to get is for a page that has a simple form of one field to change a user's email address using an UpdateView.
Sounds simple, but the difficulty is that I want the URL mapping url(r'email/(?P<pk>\d+)/$', EmailView.as_view(),) not to use the id of the Model used in my ModelForm (User) but the id of another Model (Profile).
The id of a Profile instance of a specific user can be called as follows inside a view: self.user.get_profile().id. I am using the Profile model of the reusable app userena if you are wondering.
A (afaik not optimally implemented ¹) feature of an UpdateView is that if you want to use your own ModelForm instead of letting the UpdateView derive a form from a Model you need to(otherwise produces an Error) define either model, queryset or get_queryset.
So for my EmailView case I did the following:
forms.py
class EmailModelForm(forms.ModelForm):
class Meta:
model = User
fields = (
"email",
)
def save(self, *args, **kwargs):
print self.instance
# returns <Profile: Billy Bob's Profile> instead of <User: Billy Bob> !!!
return super(EmailModelForm, self).save(*args, **kwargs)
views.py
class EmailView(UpdateView):
model = Profile # Note that this is not the Model used in EmailModelForm!
form_class = EmailModelForm
template_name = 'email.html'
success_url = '/succes/'
I then went to /email/2/. That is the email form of the user that has a profile with id 2.
If I would run a debugger inside EmailView I get this:
>>> self.user.id
1
>>> profile = self.user.get_profile()
>>> profile.id
2
So far so good. But when I submit the form it won't save. I could overwrite the save method in the EmailModelForm but I'd rather override something in my EmailView. How can I do that?
¹ Because UpdateView could just derive the model class from the ModelForm passed to the form_class attribute in case it is a ModelForm.
Having your view and model form correspond to different models seems a bad idea to me.
I would set model = User in your EmailView, then override get_object so that it returns the user corresponding to the given profile id.

Django admin: ManyToManyField in list_editable?

In the Django admin, I would really like to be able to display an editable ManyToManyField in the list display.
It doesn't necessarily need to be the full ManyToManyField control - being able to save just one value would be good enough for the purposes of the list display (though the underlying values are many-to-many in nature).
My model looks like this:
class Item(models.Model):
name = models.CharField(max_length=500)
colour = models.ManyToManyField(Colour, related_name='primary_colour')
If I try this in admin.py:
class ItemAdmin(admin.ModelAdmin):
list_display = ('name', 'colour')
list_editable = ('colour')
Then I get this error:
'ItemAdmin.list_display[6]', 'colour' is a ManyToManyField which is not supported.
Is there any way at all that I can show an editable ManyToManyField for rapid editing in the list display?
I found this related question, which explains how to make the values visible in the list display, but not editable: ManyToManyField widget in a django admin change list?
Django by default won't allow to add ManyToManyField in list_editable in ModelAdmin. So we need to override model admin methods.
On looking your models you need to follow below steps to get the ManyToManyField editable in list display page.
In apps/forms.py you need to define which ManyToMany fields you need to make editable in list display page. As below,
from django import forms
from app.models import Item
class ItemChangeListForm(forms.ModelForm):
# here we only need to define the field we want to be editable
colour = forms.ModelMultipleChoiceField(queryset=Colour.objects.all(),
required=False)
In app/admin.py you need to override methods of model admin. As below,
from django.contrib import admin
from django.contrib.admin.views.main import ChangeList
from app.models import Item
from app.forms import ItemChangeListForm
class ItemChangeList(ChangeList):
def __init__(self, request, model, list_display,
list_display_links, list_filter, date_hierarchy,
search_fields, list_select_related, list_per_page,
list_max_show_all, list_editable, model_admin):
super(ItemChangeList, self).__init__(request, model,
list_display, list_display_links, list_filter,
date_hierarchy, search_fields, list_select_related,
list_per_page, list_max_show_all, list_editable,
model_admin)
# these need to be defined here, and not in ItemAdmin
self.list_display = ['action_checkbox', 'name', 'colour']
self.list_display_links = ['name']
self.list_editable = ['colour']
class ItemAdmin(admin.ModelAdmin):
def get_changelist(self, request, **kwargs):
return ItemChangeList
def get_changelist_form(self, request, **kwargs):
return ItemChangeListForm
admin.site.register(Item, ItemAdmin)
Now you all set to check the changes, run server and check django admin for Movie model. You can edit ManyToMany field directly from list display page.
Note : If you are going to use muliptle ManyToManyFields editable in list then, you need to set DATA_UPLOAD_MAX_NUMBER_FIELDS in settings.py .
You can easily add a custom view to your admin urls and add the required html/javascript/ajax. Here's the basics:
class ItemAdmin(admin.ModelAdmin):
# regular stuff
def render_foo(self, obj):
# add this to your list_display
html = '<stuff><input/submit action></stuff>'
return mark_safe(html)
def get_urls(self):
urls = super(ItemAdmin, self).get_urls()
extra_urls = patterns('',
(r'^process_foo/$', self.admin_site.admin_view(self.process_foo)),
)
return extra_urls + urls
def process_foo(self, request):
if not request.is_ajax():
raise Http404
foo = request.GET.get("attr")
# process m2m
# return some json

Django admin site: How does the Add User page work (more fields on edit)?

I was wondering how they made it possible to display more fields in the User page of the Django admin site.
If you create a new User you only have some basic fields to fill in, but if you reopen that user (edit mode) then you see a lot more fields to fill in.
I'm trying to achieve the same, I had a look at the add_form.html template but I can't really get my head around it. I guess I'm looking for a way of specifying different fields = [] sets based on the edit status of the document.
Thanks!
The answer lies in the custom admin class registered for the User model. It overrides a couple of methods on ModelAdmin and checks to see whether the current request is creating a new User (in which case the bare-bones form class for adding accounts is used) or editing an existing one (in which case a full form is shown).
Here's my try. When I try to create a new item (Add) it shows only certain fields but then when I hit save it returns an error:
DoesNotExist
in /Library/Python/2.6/site-packages/django/db/models/fields/related.py in get, line 288
admin.py
from django.contrib import admin
from myapp.catalog.models import Model
from myapp.catalog.forms import ProductAdminForm, ProductAddForm
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
#...
add_form = ProductAddForm
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults.update({
'form': self.add_form,
})
defaults.update(kwargs)
return super(ProductAdmin, self).get_form(request, obj, **defaults)
forms.py
from myapp.catalog.models import Product
class ProductAdminForm(forms.ModelForm):
class Meta:
model = Product
#...
class ProductAddForm(forms.ModelForm):
class Meta:
model = Product
fields = ("model", "colour",)

Automatic author in Django admin

all. I'm working on the admin for my django site, and I've run into an obstacle.
I've got an Entry model and a Related model. The Related model has two foreign key fields: one to the Entry model (entry) and one to django's User model (author). The Related model is considered a "sub-model" of the Entry model, and each user can only have one Related per Entry.
In the admin, Related is edited inline with Entry. As I have it, the admin shows only one extra Related at a time, and it automatically fills the author field with the current user:
from django.contrib import models
from django.contrib.auth.models import User
class Entry(models.Model):
pass
class Related(models.Model):
entry = models.ForeignKey(Entry)
author = models.ForeignKey(User)
class Meta:
unique_together = ('entry', 'author')
from django.contrib import admin
class RelatedInline(admin.StackedInline):
model = Related
exclude = ('author',)
max_num = 1
class EntryAdmin(admin.ModelAdmin):
inlines = (RelatedInline,)
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in filter(lambda obj: isinstance(obj, Related), instances):
if instance.__dict__.get('author', None) is None:
instance.author = request.user
instance.save()
formset.save_m2m()
The problem is that if a user wants to edit an entry which already has a Related by anyone, then only that one related field will be shown.
If possible, I wonder if anyone has any ideas about how I could keep a setup similar to this, but have the admin automatically display the user's related if it exists and an empty form if it doesn't. Barring that, I would just get rid of the line max_num = 1 and replace it with extra = 1. Of course, this would mean that a "new related" form would show even if the user already had one for the current entry, so I wonder if anyone has any idea about how I would catch a possible IntegrityError and let the user know that an error had occurred.
It turns out this is pretty simple. You just need to add a queryset function to your RelatedInline class, specifying which inline to show. If the returned queryset has at least one member, the first will be shown. If the queryset is empty, a single blank inline will be shown!
class RelatedInline(admin.StackedInline):
model = Related
exclude = ('author',)
max_num = 1
def queryset(request):
return Related.objects.filter(author = request.user)