This should be a pretty straight-forward thing, but it's not working as it should.
model
class Lesson(models.Model):
lesson_name = models.CharField(max_length=150, primary_key=True)
json_name = models.CharField(max_length=150, default="", null=True)
activity_type = models.CharField(max_length=20)
learning_language = models.CharField(max_length=50)
known_language = models.CharField(max_length=50)
admin
from .models import Lesson
class LessonAdmin(admin.ModelAdmin):
fields = ['lesson_name']
readonly_fields = ['json_name', 'activity_type', 'learning_language', 'known_language']
admin.site.register(Lesson, LessonAdmin)
But, when I log into the admin site, the fields specified in readonly_fields don't show up at all. I've also tried including the field names in a tuple instead of a list (as the documentation specifies), but that gives me an error even.
The actual fields displayed are only set in list_display, fields and fieldsets. readonly_fields just tells the admin, of those that it would display, which to display as read-only. So just add the same fields to your fields list as well. It's duplication, which is annoying, but more explicit.
class LessonAdmin(admin.ModelAdmin):
fields = ['lesson_name', 'json_name', 'activity_type', 'learning_language', 'known_language']
readonly_fields = ['json_name', 'activity_type', 'learning_language', 'known_language']
And it doesn't matter if you use a tuple or a list, the django admin accepts both.
Related
I have two related models.
class Refbook(models.Model):
code = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=300)
description = models.TextField()
class VersionRefbook(models.Model):
refbook_id = models.ForeignKey('Refbook',
on_delete=models.CASCADE,
related_name='versions')
version = models.CharField(max_length=50)
date = models.DateField()
When I edit a Refbook instance in the admin panel, I want the read-only list of available versions of this Refbook instance to be displayed on the same page.
I know that it is possible to output them through TabularInline . And it seems that there is a read-only property here.
Maybe there is a way to display just a list in a column or row separated by commas?
Now, I have this code in admin.py:
#admin.register(Refbook)
class RefbookAdmin(admin.ModelAdmin):
list_display = ['id', 'code', 'name']
I tried to create a "get_versions(self)" method in models.py in Refbook class, in which I received a queryset using related_name. But I can't display it in the admin panel. Or is it still correct to do this using the model.ModelAdmin parameters?
You can do something like this:
#admin.register(Refbook)
class RefbookAdmin(admin.ModelAdmin):
list_display = ['id', 'code', 'name', 'versions']
def versions(self, obj):
return ', '.join(obj.versions.values_list('version', flat=True))
I got problem on limiting field shown in forms.ModelForm.
I Use Django 2.2
Currently I have
models.py
class MyModel(models.Model) :
user = models.ForeignKey(User, on_delete=models.CASCADE)
justchar = models.CharField(max_length=20, blank=True, null=True)
admins.py
class MyModelAdmin(admin.ModelAdmin) :
form=MyModelForm
admin.site.register(MyModel,MyModelAdmin)
form.py
class MyModelForm(forms.ModelForm) :
class Meta:
fields = ['user']
But the form still shows all fields.
I also tried with 'exclude', but got same results
You don't need a form for this. In fact, as the admin docs explicitly state, the fields attribute on a modelform is ignored in the admin.
Instead, just set fields directly on the admin class:
class MyModelAdmin(admin.ModelAdmin) :
fields = ['user']
admin.site.register(MyModel,MyModelAdmin)
I have 2 models:
class CompanyInfo(models.Model):
name = models.CharField(_('name'), max_length=100)
address = models.OneToOneField(Location)
class Location(models.Model):
address_1 = models.CharField(_("address"), max_length=128)
address_2 = models.CharField(_("address cont'd"), max_length=128, blank=True)
city = models.CharField(_("city"), max_length=64, default="")
state = USStateField(_("state"), default="")
zip_code = models.CharField(_("zip code"), max_length=5, default="")
When I use CBVs and prints out the form on the template. It shows name with an input field and address as a multiple choice selection.
Is there anyway in which I could convert the multiple choice to act as multiple input fields and the state to be multiple choice.
I figured I could have 2 form for these, but how would I incorporate a form inside of a form?
Also, the way in which the models and fields are must not be changed. For this example sure location fields could be in the company info, but I simply want how to do something similar when it doesn't make sense to include all these fields into the same model.
So, far only thing I can come up with is using function based views and just dealing with 2 forms at a time.
Here are my forms:
class CompanyInfoForm(ModelForm):
class Meta:
model = CompanyInfo
exclude = ['address']
class LocationForm(ModelForm):
class Meta:
model = Location
fields = '__all__'
then class based views:
class CompanyInfoCreate(CreateView):
model = CompanyInfo
form_class = CompanyInfoForm
class LocationCreate(CreateView):
model = Location
form_class = LocationForm
However, this is very helpful since these forms can only be done 1 at a time. I would like LocationView to be in place of the address location or of the sort.
Perhaps, these types of views have their strength in dealing with forms at an individual level.
I have a form like this:
class My_Form(ModelForm):
class Meta:
model = My_Class
fields = ('first_name', 'last_name' , 'address')
How can I set the address field as optional?
class My_Form(forms.ModelForm):
class Meta:
model = My_Class
fields = ('first_name', 'last_name' , 'address')
def __init__(self, *args, **kwargs):
super(My_Form, self).__init__(*args, **kwargs)
self.fields['address'].required = False
Guess your model is like this:
class My_Class(models.Model):
address = models.CharField()
Your form for Django version < 1.8:
class My_Form(ModelForm):
address = forms.CharField(required=False)
class Meta:
model = My_Class
fields = ('first_name', 'last_name' , 'address')
Your form for Django version > 1.8:
class My_Form(ModelForm):
address = forms.CharField(blank=True)
class Meta:
model = My_Class
fields = ('first_name', 'last_name' , 'address')
field = models.CharField(max_length=9, default='', blank=True)
Just add blank=True in your model field and it won't be required when you're using modelforms.
"If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True."
source:
https://docs.djangoproject.com/en/4.1/topics/forms/modelforms/#field-types
[Edit]: Change django doc link from 3.1 to 4.1
You would have to add:
address = forms.CharField(required=False)
Solution: use both blank=True, null=True.
my_field = models.PositiveIntegerField(blank=True, null=True)
Explanation:
If you use null=True
my_field = models.PositiveIntegerField(null=True)
then my_field is required, with * next to it in the form and you can't submit the empty value.
If you use blank=True
my_field = models.PositiveIntegerField(blank=True)
then my_field is not required, there won't be a * next to it in the form and you can't submit the value. But it will get null field not allowed.
Note: marking as not required and allowing null fields are two different things.
Pro Tip: Read the error more carefully than documentation.
#Anentropic's solution from the comment on #Atma's answer worked for me. And I think it's the best one too.
His comment:
null=True, blank=True will cause the ModelForm field to be required=False
I just set it on my ManyToMany field in my UserProfile class and it worked flawlessly.
My UserProfile class now looks like this (notice the friends field):
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
friends = models.ManyToManyField('self', null=True, blank=True)
I also think that this is the most beautiful solution since you do the same thing, put null and blank to True, weather you have a simple char field or, like I have, ManyToMany field.
The above answers are correct; nevertheless due note that setting null=True on a ManyToManyField has no effect at the database level and will raise the following warning when migrating:
(fields.W340) null has no effect on ManyToManyField.
A good answer to this is explained in this other thread.
Let's say I'm using the default auth.models.User plus my custom Profile and Address models which look like this:
class Profile(models.Model):
user = models.OneToOneField(User)
primary_phone = models.CharField(max_length=20)
address = models.ForeignKey("Address")
class Address(models.Model):
country = CountryField(default='CA')
province = CAProvinceField(default='BC')
city = models.CharField(max_length=80)
postal_code = models.CharField(max_length=6)
street1 = models.CharField(max_length=80)
street2 = models.CharField(max_length=80, blank=True, null=True)
street3 = models.CharField(max_length=80, blank=True, null=True)
Now I want to create a registration form. I could create a ModelForm based on User but that won't include fields for the Profile and Address (which are required). So what's the best way to go about building this form? Should I even use ModelForm at all?
Furthermore, how would I use the same form for editing the complex object? I could easily pass an instance of Profile back to it, which holds references to the necessary Address and Profile objects, but how do I get it to fill in the fields for me?
What about using 3 separate ModelForm. One for Address, one for User, and one for Profile but with :
class ProfileForm(ModelForm):
class Meta:
model = Profile
exclude = ('user', 'address',)
Then, process these 3 forms separately in your views. Specifically, for the ProfileForm use save with commit=False to update user and address field on the instance :
# ...
profile_form = ProfileForm(request.POST)
if profile_form.is_valid():
profile = profile_form.save(commit=False)
# `user` and `address` have been created previously
# by saving the other forms
profile.user = user
profile.address = address
Don't hesitate to use transactions here to be sure rows get inserted only when the 3 forms are valid.
You should look into the officially recommended way to extend the User model first, as seen in the docs, which I believe comes directly from the project manager's personal blog about the subject. (The actual blog article is rather old, now)
As for your actual issue with forms, have a look at the project manager's own reusable django-profiles app and see if perusing the code solves your issue. Specifically these functions and the views in which they are utilized.
Edited to Add:
I've looked into it a bit (as I needed to do so myself). It seems something like so would be sufficient:
# apps.profiles.models
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
...
birth_date = models.DateField(blank=True, null=True)
joined = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'user profile'
verbose_name_plural = 'user profiles'
db_table = 'user_profiles'
class Address(models.Model):
user = models.ForeignKey(UserProfile)
...
# apps.profiles.forms
from django import forms
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from django.contrib.auth.models import User
from apps.profiles.models import UserProfile, Address
class UserForm(ModelForm):
class Meta:
model = User
...
class UserProfileForm(ModelForm):
class Meta:
model = UserProfile
...
AddressFormSet = inlineformset_factory(UserProfile, Address)
I was using "..." to snip content in the code above. I have not yet tested this out but from looking through examples and the documentation on forms I believe this to be correct.
Note I put the FK from the Address model to the UserProfile and not the other way around, as in your question. I believe the inline formsets need this to work correctly.
Then of course in your views and templates you will end up treating UserForm, UserProfileForm, and AddressFormSet separately but they can all be inserted into the same form.
I think your are looking for inline formsets with model forms. This helps you to deal with multiple forms on one page and also takes care of foreign key relations.
Update:
Maybe this question helps you too: Django: multiple models in one template using forms