ModelForm field included even when not in fields - django

I have the following model:
class Test(models.Model):
name = models.CharField(max_length=100)
And the admin:
class TestForm(forms.ModelForm):
confirm_name = forms.CharField(max_length=100)
...
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
form = TestForm
fields = ('name',)
create_fields = ('name', 'confirm_name')
def get_fields(self, request, obj=None):
fields = super().get_fields(request, obj)
if not obj:
fields = self.create_fields
return fields
Everything works fine. But when you add a record and then try to edit it, I get the error "Please correct the error below." without showing any field errors. I checked the form errors and it says confirm_name should not be empty. Why is it still being included if it's not added in fields?

You need to use add_form instead of get_fields.
In your example
class CreateTestForm(forms.ModelForm):
name = models.CharField(max_length=100)
confirm_name = models.CharField(max_length=100)
#.... your validation logic
class UpdateTestForm(forms.ModelForm):
name = models.CharField(max_length=100)
and in your admin.py
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
form = TestForm
add_form = CreateTest
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during test creation
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
Credits from django.contrib.auth.admin

Field for admin form are from TestForm
class TestForm(forms.ModelForm):
confirm_name = forms.CharField(max_length=100)
You can use:
class CreateTestForm(forms.ModelForm):
confirm_name = forms.CharField(max_length=100)
class Meta:
model = Model
field = ('name', 'confirm_name')
class UpdateTestForm(forms.ModelForm):
class Meta:
model = Model
field = ('name',)
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, change=False, **kwargs):
if obj:
kwargs['form'] = UpdateTestForm
else:
kwargs['form'] = CreateTestForm
return super().get_form(request, obj, change, **kwargs)

Related

Django limit choice of user field foreign key based on the user that logged in

I have a model called Client with user field as a foreign key:
class Client(models.Model):
name = models.CharField(_('Client Name'), max_length=100)
address = models.CharField(_('Client Address'), max_length=100, blank=True)
demand = models.PositiveIntegerField(_('Client Demand'))
location = models.PointField(_('Client Location'))
created_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
class Meta:
default_permissions = ('add', 'change', 'delete', 'view')
def __str__(self):
return self.name
I want to limit the choice of the user field in the admin form based on who logged in
for example, here I logged in as agung, so I want the select box choice of user field limit only to agung, but here I can access other username like admin and rizky.
I tried this
class ClientAdminForm(forms.ModelForm):
class Meta:
model = Client
fields = "__all__"
def __init__(self, request, *args, **kwargs):
super(ClientAdminForm, self).__init__(request, *args, **kwargs)
if self.instance:
self.fields['user'].queryset = request.user
but it seems that it can't take request as an argument (I guess because this is not an Http request)
You can overwrite your Admin Model's get_form method to add the current request.user as class property. Next you can read it in the Form's constructor and filter the query.
class ClientAdmin(admin.ModelAdmin):
# [...]
def get_form(self, request, obj=None, **kwargs):
form_class = super(ClientAdmin, self).get_form(request, obj, **kwargs)
form_class.set_user(request.user)
return form_class
class ClientAdminForm(forms.ModelForm):
# [...]
#classmethod
def set_user(cls, user):
cls.__user = user
def __init__(self, *args, **kwargs):
super(ClientAdminForm, self).__init__(*args, **kwargs)
self.fields['user'].queryset = \
self.fields['user'].queryset.filter(pk=self.__user.pk)
However, is easiest exclude this field in form and update it in the save_model method:
class ClientAdmin(admin.ModelAdmin):
# [...]
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
You can do it by override the base_fields attribute of your form instance like this :
views.py
# Before instantiate the form class
ClientAdminForm.base_fields['user'] = forms.ModelChoiceField(queryset=self.request.user)
# Now you can instantiate the form
form = ClientAdminForm(...)
NB : Do override the base_fields just before instantiate the form

I want to dynamically change the form of django according to user information

Although it is in the title, I want to change the form dynamically with django.
But now I get an error.
I can't deal with it.
I was able to get user information, but if I filter it, it will be “cannot unpack non-iterable UPRM object”.
#forms.py
class RecordCreateForm(BaseModelForm):
class Meta:
model = URC
fields = ('UPRC','URN','UET','URT',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(RecordCreateForm,self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
self.fields['URN'].choices = UPRM.objects.filter(user=user)
#views.py
class RecordCreate(CreateView):
model = URC
form_class = RecordCreateForm
template_name = 'records/urcform.html'
success_url = reverse_lazy('person:home')
def get_form_kwargs(self):
kwargs = super(RecordCreate, self).get_form_kwargs()
# get users, note: you can access request using: self.request
kwargs['user'] = self.request.user
return kwargs
#models
class UPRM(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
URN = models.CharField( max_length=30,editable=True)
def __str__(self):
return self.URN
class URC(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
UPRC = models.CharField(max_length=300)
URN = models.ForeignKey(UPRM, on_delete=models.CASCADE)
def __str__(self):
return self.UPRC
cannot unpack non-iterable UPRM object
You should use queryset instead of choices here:
class RecordCreateForm(BaseModelForm):
class Meta:
model = URC
fields = ('UPRC','URN','UET','URT',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(RecordCreateForm,self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
self.fields['URN'].queryset = UPRM.objects.filter(user=user)

Display auth.User fields in related model

I have created this Django model:
class Teacher(models.Model):
user = models.OneToOneField('auth.User', on_delete=models.CASCADE, default=1)
surname = models.CharField(max_length=15, null=True)
creation_date = models.DateTimeField('creation date', auto_now_add=True)
deletion_date = models.DateTimeField('deletion date', null=True, blank=True)
and a form to represent it in read-only mode:
class TeacherViewForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TeacherViewForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
for field in self.fields:
self.fields[field].widget.attrs['disabled'] = True
class Meta:
model = Teacher
exclude = ['deletion_date']
The view looks like this:
class TeacherViewDetail(generic.DetailView):
model = Teacher
template_name = 'control/teacher_detail.html'
form_class = TeacherViewForm
def get_form(self):
# Instantiate the form
form = self.form_class(instance=self.object)
return form
def get_context_data(self, **kwargs):
context = super(TeacherViewDetail, self).get_context_data(**kwargs)
context.update({
'form': self.get_form(),
})
return context
As you can see, there is a OneToOne relation between the Teacher and the auth.User models. I need to displayfirst_name and last_name from auth.User model, but only the username is shown.
How can I display these fields the same way field Teacher.surname is being displayed?
Do I have to include them in the model, the form.fields or is there a property I have to modify in order to achieve it?
Thanks
You would have to remove the user field and add the appropriate fields instead. Then in your form's __init__, fill the initial values:
# forms.py
class TeacherViewForm(ModelForm):
first_name = forms.CharField()
last_name = forms.CharField()
def __init__(self, *args, **kwargs):
super(TeacherViewForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
for field in self.fields:
self.fields[field].widget.attrs['disabled'] = True
# set initial values
self.fields['first_name'].initial = instance.user.first_name
self.fields['last_name'].initial = instance.user.last_name
class Meta:
model = Teacher
exclude = ['user', 'deletion_date'] # exclude user
But why do you use a form at all if you disable all fields? You can just render all this information much more freely in the template.

MultipleObjectsReturned at ... get() returned more than one Person -- it returned 2

I am using these model:
class Person(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
name_in_bangla = models.CharField(max_length=100)
nick_name = models.CharField(max_length=30)
birth_date = models.DateField()
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name='member_persons')
class Meta:
ordering = ['name']
unique_together = ['name', 'birth_date']
my views:
class PersonCreate(SuccessMessageMixin,CreateView):
model = Person
form_class = MemberForm
success_message = "%(name)s was added as %(category)s successfully."
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.request = self.request
return form
my views:
class MemberForm(ModelForm):
class Meta:
model = Person
exclude =('user',)
def clean(self):
user = get_user(self.request)
name = self.cleaned_data.get('name')
birth_date = self.cleaned_data.get('birth_date')
if self.instance.id:
if Person.objects.filter(user=user).exclude(id=self.instance.id).exists():
self.add_error('name', "You already submitted data")
elif Person.objects.filter(name=name, birth_date=birth_date).exclude(id=self.instance.id).exists():
self.add_error('name', "Person with this Name and Birth date already exists.")
else:
if Person.objects.filter(user=user).exists():
self.add_error('name', "You already submitted data")
elif Person.objects.filter(name=name, birth_date=birth_date).exists():
self.add_error('name', "Person with this Name and Birth date already exists.")
return self.cleaned_data
def save(self, commit=True):
person = super().save(commit=False)
if not person.pk:
person.user = get_user(self.request)
if commit:
person.save()
self.save_m2m()
return person
It works fine for different persons. But when I try to use same name for person but different birth date it gives MultipleObjectReturn with 'get() returned more than one Person -- it returned 2!'
But I used unique_together = ['name', 'birth_date'] constraints as well as I use clean method to catch the constraints errors in forms. But my forms not catching MultipleObjectReturned errors.
How could I catch these errors in my forms? What my codes doing wrong. Any body suggestions will be appreciated.
MY other views:
class PersonDetailView(generic.DetailView):
queryset = (
Person.objects.all()
.prefetch_related('child_set')
# below omitted because of with tag
# and conditional display based on time
# .prefetch_related('blog_posts')
)
class PersonUpdate(SuccessMessageMixin,UpdateView):
model = Person
form_class = MemberForm
success_message = "%(category)s: %(name)s was updated successfully."
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.request = self.request
return form
#require_authenticated_permission(
'member.delete_person')
class PersonDelete(DeleteView):
model = Person
success_url = '/allmember/'
success_message = "%(category)s: %(name)s was deleted successfully."
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super(PersonDelete, self).delete(request, *args, **kwargs)
It looks like you have two users with the same slug. If your url (which you haven't shown) is /person/<slug>/, then you need to set unique=True for your slug field.

Extend UserCreationForm for extended User in Django

I extended my django user and need to create a registration form now.
I got most of it figured out but I don't know how to exclude fields I don't need during registration. Right know I see all fields in the registration form.
Here is the code:
models.py
class Artist(Model):
user = OneToOneField(User, unique=True)
address = CharField(max_length=50)
city = CharField(max_length=30)
ustid = CharField(max_length=14)
date_of_birth = DateField()
bio = CharField(max_length=500)
def __unicode__(self):
return self.user.get_full_name()
User.profile = property(lambda u: Artist.objects.get_or_create(user=u)[0])
forms.py
class RegistrationForm(UserCreationForm):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
artist_kwargs = kwargs.copy()
if kwargs.has_key('instance'):
self.artist = kwargs['instance'].artist
artist_kwargs['instance'] = self.artist
self.artist_form = ArtistForm(*args, **artist_kwargs)
self.fields.update(self.artist_form.fields)
self.initial.update(self.artist_form.initial)
def clean(self):
cleaned_data = super(RegistrationForm, self).clean()
self.errors.update(self.artist_form.errors)
return cleaned_data
def save(self, commit=True):
self.artist_form.save(commit)
return super(RegistrationForm, self).save(commit)
How do I exclude fields?
class Meta:
model = User
exclude = ('bio',)
You can't include or exclude fields that are not a member of the meta model.
What you can do is doing that in each form. In this case the UserCreationForm is extended by the ArtistForm. Just restrict the fields in the form that belong to the right meta model.