I use abstract models in Django like:
class Tree(models.Model):
parent = models.ForeignKey('self', default=None, null=True, blank=True,
related_name="%(app_label)s_%(class)s_parent")
class Meta:
abstract = True
class Genre(Tree):
title = models.CharField(max_length=150)
And all fields from the abstract model go first in Django's admin panel:
parent:
abstract_field2:
title:
model_field2:
...
Is there a way to put them (fields from abstract classes) in the end of the list?
Or a more general way to define order of fields?
You can order the fields as you wish using the ModelAdmin.fields option.
class GenreAdmin(admin.ModelAdmin):
fields = ('title', 'parent')
Building off rbennell's answer I used a slightly different approach using the new get_fields method introduced in Django 1.7. Here I've overridden it (the code would be in the definition of the parent's ModelAdmin) and removed and re-appended the "parent" field to the end of the list, so it will appear on the bottom of the screen. Using .insert(0,'parent') would put it at the front of the list (which should be obvious if you're familiar with python lists).
def get_fields (self, request, obj=None, **kwargs):
fields = super().get_fields(request, obj, **kwargs)
fields.remove('parent')
fields.append('parent') #can also use insert
return fields
This assumes that your fields are a list, to be honest I'm not sure if that's an okay assumption, but it's worked fine for me so far.
I know it's an old question, but wanted to thow in my two cents, since my use case was exactly like this, but i had lots of models inheriting from one class, so didn't want to write out fields for every admin. Instead I extended the get_form model and rearranged the fields to ensure parent always comes at the end of the fields in the admin panel for add/change view.
class BaseAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(BaseAdmin, self).get_form(request, obj, **kwargs)
parent = form.base_fields.pop('parent')
form.base_fields['parent '] = parent
return form
base_fields is an OrderedDict, so this appends the 'parent' key to the end.
Then, extend this admin for any classes where you want parent to appear at the end:
class GenreAdmin(BaseAdmin):
pass
This simple solution from #acquayefrank (in the comments) worked for me:
The order of fields would depend on the order in which you declare them in your models.
Related
I have a (horrible) database table that will be imported from a huge spreadsheet. The data in the fields is for human consumption and is full of "special cases" so its all stored as text. Going forwards, I'd like to impose a bit of discipline on what users are allowed to put into some of the fields. It's easy enough with custom form validators in most cases.
However, there are a couple of fields for which the human interface ought to be a ChoiceField. Can I override the default form field type (CharField)? (To clarify, the model field is not and cannot be constrained by choices, because the historical data must be stored. I only want to constrain future additions to the table through the create view).
class HorribleTable( models.Model):
...
foo = models.CharField( max_length=16, blank=True, ... )
...
class AddHorribleTableEntryForm( models.Model)
class Meta:
model = HorribleTable
fields = '__all__' # or a list if it helps
FOO_CHOICES = (('square', 'Square'), ('rect', 'Rectangular'), ('circle', 'Circular') )
...?
Perhaps you could render the forms manually, passing the options through the context and make the fields in html.
Take a look at here:https://docs.djangoproject.com/en/4.0/topics/forms/#rendering-fields-manually
I think you can easily set your custom form field as long it will match the data type with the one set in your model (e.g. do not set choices longer than max_length of CharField etc.). Do the following where foo is the same name of the field in your model:
class AddHorribleTableEntryForm(forms.ModelForm):
foo = forms.ChoiceField(choices=FOO_CHOICES)
class Meta:
model = HorribleTable
...
I think this is perfectly fine for a creation form. It's will not work for updates as the values in the DB will most probably not match your choices. For that, I suggest adding a second form handling data updates (maybe with custom permission to restrict it).
UPDATE
Another approach will be to override the forms init method. That way you can handle both actions (create and update) within the same form. Let the user select from a choice field when creating an object. And display as a normal model field for existing objects:
class AddHorribleTableEntryForm(forms.ModelForm):
foo = forms.ChoiceField(choices=FOO_CHOICES)
class Meta:
model = HorribleTable
fields = '__all__' # or a list if it helps
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get("instance", None)
if instance is None:
self.fields["foo"].widget = forms.widgets.Select(choices=self.FOO_CHOICES)
Is it possible to change what fields are displayed in a ModelForm, dynamically?
I am trying to show only a small number of fields in a ModelForm when the user adds a new instance (of the Model) from the frontend (using an add form) but larger number of fields when the user edits an instance (using an edit form).
The Form class looks something like this:
class SchoolForm(ModelForm):
class Meta:
model = School
#want to change the fields below dynamically depending on whether its an edit form or add form on the frontend
fields = ['name', 'area', 'capacity', 'num_of_teachers']
widgets = {
'area': CheckboxSelectMultiple
}
labels = {
'name': "Name of the School",
'num_of_teachers': "Total number of teachers",
}
Trying to avoid having two separate classes for add and edit since that doesnt seem DRYish. I found some SO posts with the same question for the admin page where we could override get_form() function but that does not apply here.
Also, this answer suggests using different classes as the normal way and using dynamic forms as an alternative. Perhaps dynamics forms is the way forward here but not entirely sure (I also have overridden __init__() and save() methods on the SchoolForm class).
I'm not suere if is a correct way, but i use some method in class to add fields or delete-it. I used like this:
class someForm(forms.ModelForm):
class Meta:
model = Foo
exclude = {"fieldn0","fieldn1"}
def __init__(self, *args, **kwargs):
super(someForm, self).__init__(*args, **kwargs)
self.fields['foofield1'].widget.attrs.update({'class': 'form-control'})
if self.instance.yourMethod() == "FooReturn":
self.fields['city'].widget.attrs.update({'class': 'form-control'})
else:
if 'city' in self.fields: del self.fields['city']
Hope it helps.
I use custom abstract model with manager throughout my project.
class BaseQueryset(models.QuerySet):
pass
class BaseManager(models.Manager):
queryset_class = BaseQueryset
def get_queryset(self, exclude_no_published=True):
""" exclude all objects with is_published=False by default """
q = self.queryset_class(self.model)
if exclude_no_published:
q = q.exclude(is_published=False)
return q
def all_objects(self):
""" allows geting all objects in admin """
return self.get_queryset(exclude_no_published=False)
class BaseAbstractModel(models.Model):
is_published = models.BooleanField(default=True)
objects = BaseManager()
class Meta:
abstract = True
All models inherit from this abstract model and I need a way to represent all objects in admin. So I wrote my own mixin for admin classes with get_queryset method
class AdminFullQuerysetMixin(object):
def get_queryset(self, request):
"""
Allows showing all objects despite on is_public=False
"""
qs = self.model.objects.all_objects()
ordering = self.get_ordering(request)
if ordering:
qs = qs.order_by(*ordering)
return qs
There is my typical admin class:
#admin.register(SomeModel)
class SomeModelAdmin(AdminFullQuerysetMixin, admin.ModelAdmin):
list_display = ('name', 'slug', 'is_published')
list_filter = ('is_published',)
list_editable = ('is_published',)
All works fine, I can see all objects in admin whether with is_published False or True. But such attributes like list_filter or list_editable don't work, when I use it in admin objects list page. There is no exception provided, just text at the top of the list: "Please correct the error below".
What methods except get_queryset should I override for solving my problem?
You may want to read this :
if you use custom Manager objects, take note that the first Manager
Django encounters (in the order in which they’re defined in the model)
has a special status. Django interprets the first Manager defined in a
class as the “default” Manager, and several parts of Django (...) will
use that Manager exclusively for that model. As a result, it’s a
good idea to be careful in your choice of default manager in order to
avoid a situation where overriding get_queryset() results in an
inability to retrieve objects you’d like to work with.
I strongly suspect you fell upon one of those cases...
The solution would then be to change your model to:
class BaseAbstractModel(models.Model):
is_published = models.BooleanField(default=True)
# this one will be the default manager
all_objects = models.Manager()
# and this one will be known as 'objects'
objects = BaseManager()
class Meta:
abstract = True
Then you can remove your AdminFullQuerysetMixin (or rewrite it's get_queryset() method to use self.model._default_manager instead)
NB : I may of course be wrong and the problem be totally unrelated ;)
I want to list only usable items in OneToOneField not all items, its not like filtering values in ChoiceField because we need to find out only values which can be used which is based on the principle that whether it has been used already or not.
I am having a model definition as following:
class Foo(models.Model):
somefield = models.CharField(max_length=12)
class Bar(models.Model):
somefield = models.CharField(max_length=12)
foo = models.OneToOneField(Foo)
Now I am using a ModelForm to create forms based on Bar model as:
class BarForm(ModelForm):
class Meta:
model = Bar
Now the problem is in the form it shows list of all the Foo objects available in database in the ChoiceField using the select widget of HTML, since the field is OneToOneField django will force to single association of Bar object to Foo object, but since it shows all usable and unusable items in the list it becomes difficult to find out which values will be acceptable in the form and users are forced to use hit/trial method to find out the right option.
How can I change this behavior and list only those items in the field which can be used ?
Although this is an old topic I came across it looking for the same answer.
Specifically for the OP:
Adjust your BarForm so it looks like:
class BarForm(ModelForm):
class Meta:
model = Bar
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
#only provide Foos that are not already linked to a Bar, plus the Foo that was already chosen for this Bar
self.fields['foo'].queryset = Foo.objects.filter(Q(bar__isnull=True)|Q(bar=self.instance))
That should do the trick. You overwrite the init function so you can edit the foo field in the form, supplying it with a more specific queryset of available Foo's AND (rather important) the Foo that was already selected.
For my own case
My original question was: How to only display available Users on a OneToOne relation?
The Actor model in my models.py looks like this:
class Actor(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name = 'peactor')
# lots of other fields and some methods here
In my admin.py I have the following class:
class ActorAdmin(admin.ModelAdmin):
# some defines for list_display, actions etc here
form = ActorForm
I was not using a special form before (just relying on the basic ModelForm that Django supplies by default for a ModelAdmin) but I needed it for the following fix to the problem.
So, finally, in my forms.py I have:
class ActorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ActorForm, self).__init__(*args, **kwargs)
#only provide users that are not already linked to an actor, plus the user that was already chosen for this Actor
self.fields['user'].queryset = User.objects.filter(Q(peactor__isnull=True)|Q(peactor=self.instance))
So here I make an ActorForm and overwrite the __init__ method.
self.fields['user'].queryset =
Sets the queryset to be used by the user formfield. This formfield is a ModelChoiceField
by default for a OneToOneField (or ForeignKey) on a model.
Q(peactor__isnull=True)|Q(peactor=self.instance)
The Q is for Q-objects that help with "complex" queries like an or statement.
So this query says: where peactor is not set OR where peactor is the same as was already selected for this actor
peactor being the related_name for the Actor.
This way you only get the users that are available but also the one that is unavailable because it is already linked to the object you're currently editing.
I hope this helps someone with the same question. :-)
You need something like this in the init() method of your form.
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
# returns Bar(s) who are not in Foo(s).
self.fields['foo'].queryset = Bar.objects.exclude(id__in=Foo.objects.all().values_list(
'bar_id', flat=True))
PS: Code not tested.
Let's say I have a single ModelForm which can be filled out by different tiers of users. The Admin can edit any field of that form; but for other users, I need to have certain fields pre-defined, and read-only and/or hidden.
Using my CBV's get_form_kwargs method, I have made the form aware of the user that's bringing it up, and, in its __init__ method I react accordingly, tweaking the form's exclude, and the fields' required and initial properties; and then, in my view's form_valid, I further enforce the values. But, frankly, I'm neither sure that every operation I do is actually needed, nor whether there's some gaping hole I'm not aware of.
So, what's the best, cleanest way of doing this?
Assuming there aren't a lot of combinations, I would create a different form that meets the different needs of your users. Then override def get_form_class and return the correct form based on your needs. This keeps the different use cases separate and gives flexibility if you need to change things in the future without breaking the other forms.
# models.py
class Foo(models.Model):
bar = model.CharField(max_length=100)
baz = model.CharField(max_length=100)
biz = model.CharField(max_length=100)
# forms.py
class FooForm(forms.ModelForm): # for admins
class Meta:
model = Foo
class FooForm(forms.ModelForm): # users who can't see bar
boo = forms.CharField()
class Meta:
model = Foo
exclude = ['bar']
class FooFormN(forms.ModelForm): # as many different scenarios as you need
def __init__(self, *args, **kwargs)
super(FooFormN, self).__init__(*args, **kwargs)
self.fields['biz'].widget.attrs['readonly'] = True
class Meta:
model = Foo
# views.py
class SomeView(UpdateView):
def get_form_class(self):
if self.request.user.groups.filter(name="some_group").exists():
return FooForm
# etc.