class Badge(Model):
# ....
class Meta:
unique_together = ('identifier', 'restaurant')
Using a CreateView, when creating a Badge object whose identifier already exists, I actually get a form error, which is the expected behaviour.
But, using an UpdateView, when editing a Badge object whose identifier already exists, I don't get any form error, but a 500 error with duplicate key value violates unique constraint.
I can't understand why the behaviour differs. I'd like the form error to be shown in both cases.
I just realised for validation to work, that all fields need to be specified in the class based view, even if these fields should not be filled by the User.
class BadgesUpdateView(UpdateView):
model = Badge
# restaurant field must be included for validation even if the user does NOT fill it.
fields = ('identifier', 'is_active', 'owner', 'restaurant')
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['restaurant'].widget = forms.HiddenInput()
return form
Related
I'm currently trying to build an application in Django where users should be able to create new entries for a model. Said model contains a ForeignKey relation to a different model. However, the classic dropdown form field is not practical, because there could be hundreds of entries to choose from. Instead, I'd like to allow users to simply type in the name of the entry (all names are unique), which then gets transformed to the corresponding ID when pressing the Submit button.
I feel like this should be pretty straightforward to do, but since I'm new to Django I'm having a hard time to figure out how to do it. Currently the problem is, that the form field is declared invalid, due to the ForeignKey not existing (since the user typed in the name and not the ID of the database entry).
Code example:
forms.py:
class DogForm(ModelForm):
class Meta:
model = Dog
fields = "__all__"
widgets = {" shelter": widgets.TextInput(attrs={"id":" shelter", "placeholder":" Dog Shelter"}),
"name": widgets.TextInput({"id":"name", "placeholder":"Name"}),
def clean(self):
shelter= self.data.get("shelter")
self.cleaned_data["shelter"] = Shelter.objects.get(name=shelter)
return self.cleaned_data
views.py:
class DogCreate(CreateView):
model = Dog
form_class = DogForm
def form_valid(self, form):
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return super(DogCreate, self).form_valid(form)
As you can see, my idea was to overwrite the clean method, by adding the correct Shelter to self.cleaned_data (based on the name the user put into the TextInputField). But this does not seem to work, because when pressing submit, the form is still declared invalid.
I study the CBV in django.
I want a user can upload images to a certain card of an apartment.
So The model was created:
class Photo(models.Model):
photo_path = models.ImageField(verbose_name='Фотография')
photo_user = models.ForeignKey(User, verbose_name='Агент, добавивший фото')
photo_flat = models.ForeignKey(Flat, verbose_name='Квартира')
photo_description = models.CharField(verbose_name='Описание', max_length=200, null=True, blank=True)
The initial data of the form are photo_user and the photo_flat.
When I try to save one or multiple pictures through a form on the page I've got AttributeError.
'NoneType' object has no attribute 'pk'
My ModelForm looks like this:
class PhotoUploadModelForm(forms.ModelForm):
class Meta:
model = Photo
fields = ['photo_path','photo_user','photo_flat','photo_description'
My CreateView:
class PhotoUploadView(CreateView):
form_class = PhotoUploadModelForm
def get_initial(self):
return {'photo_user': self.request.user,
'photo_flat': self.object.pk
}
def get_success_url(self):
return reverse('ha:flatdetail')
and my urls.py
url(r'^dev/flat/(?P<flat_id>[0-9]+)/$', views_dev.flat_ajax, name='flatdetail'),
url(r'^dev/photo-update/$', views_dev.PhotoUploadView.as_view(), name='image-update')
I understand the error but has not enough skills to handle it. am I correct that self.object.pk would be the id of the picture but not the apartment?
Can I get the id of the apartment?
You're misinterpreting the error, but that is partly because what you're doing doesn't make a lot of sense.
self.object can only refer to the object that this view is concerned with, which in this case is the Photo; but of course that photo doesn't exist yet when you do get_initial, because it hasn't been created. It seems that you want to use the ID of a specific Flat object; in which case you would need to pass that ID to the view, ie via the URL. You do this already in your flat_ajax view; you should do exactly the same thing here, and then you will be able to reference the ID via self.kwargs['flat_id'].
Note that get_initial isn't really the best place to be doing this, anyway. The User and the Flat object are not values that you want to pass to the form so that they populate fields initially but let the user change them; they are values you want to automatically associate with the object on save. So, this should really be done in form_valid, as shown in the documentation:
def form_valid(self, form):
form.instance.photo_user = self.request.user
form.instance.photo_flat_id = self.kwargs['flat_id']
return super(PhotoUploadView, self).form_valid(form)
If you do it this way, you should remove photo_user and photo_flat from the fields listed in the form.
I came across this code:
drinker/models.py:
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
class Drinker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
drinker/forms.py:
from django import forms
from django.contrib.auth.models import User
from django.forms import ModelForm
from drinker.models import Drinker
class RegistrationForm(ModelForm):
username = forms.CharField(label=(u'User Name'))
email = forms.EmailField(label=(u'Email Address'))
password = forms.CharField(label=(u'Password'), widget=forms.PasswordInput(render_value=False))
password1 = forms.CharField(label=(u'Verify Password'), widget=forms.PasswordInput(render_value=False))
class Meta:
model = Drinker
exclude = ('user',)
def clean_username(self):
username = self.cleaned_data['username']
try:
User.objects.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError("That username is already taken, please select another.")
def clean(self):
if self.cleaned_data['password'] != self.cleaned_data['password1']:
raise forms.ValidationError("The passwords did not match. Please try again.")
return self.cleaned_data
My Question is about the inner class meta which as two attributes:
model=Drinker
exclude=('user`,)
I have a not-so-clear understanding of how this meta class work. I have read the documentation but I am still confused. Can you kindly explain what those two lines mean and what their purpose is?
Thanks
The exclude attribute tells Django what fields from the model not to include in the form.
Quoting the Selecting fields to use section of the model form documentation:
2. Set the exclude attribute of the ModelForm’s inner Meta class to a list of fields to be excluded from the form.
The model line simply tells Django what model to take the fields from; together the two lines tell Django to give RegistrationForm fields based on all fields on the Drinker model, except 'user'. For the given Drinker model, that's birthday and name.
These fields are added to the other form fields already defined on the form. If the Drinker model gained more fields, those would automatically be part of the form too.
See the Overriding the default fields section of the same chapter:
When you explicitly instantiate a form field like this, it is important to understand how ModelForm and regular Form are related.
ModelForm is a regular Form which can automatically generate certain fields. The fields that are automatically generated depend on the content of the Meta class and on which fields have already been defined declaratively. Basically, ModelForm will only generate fields that are missing from the form, or in other words, fields that weren’t defined declaratively.
The inner Meta class is just a convenient way to create a namespace for such configuration on your form class for the Django framework to find. All Django now has to do is introspect Form.Meta and see what attributes are defined there.
Note that using exclude can lead to security problems. From the same documenation:
It is strongly recommended that you explicitly set all fields that should be edited in the form using the fields attribute. Failure to do so can easily lead to security problems when a form unexpectedly allows a user to set certain fields, especially when new fields are added to a model. Depending on how the form is rendered, the problem may not even be visible on the web page.
The alternative approach would be to include all fields automatically, or blacklist only some. This fundamental approach is known to be much less secure and has led to serious exploits on major websites (e.g. GitHub).
fields = exclude() and fields = '__all__' - means display all the fields
exclude = ('password',) - means exclude password field
fields = ('user','email',) - means display only email field and userfield
in short : fields you want to show up in the form should be mentioned in 'fields' attribute ex:
fields = '__all__' #will show all the fields from the model in the form
'exclude' does the opposite
exclude = ['title'] # don't show the title field
This question already has answers here:
How can I order fields in Django ModelForm?
(2 answers)
Closed 9 years ago.
trying to alter order of fields in admin ModelForm.
Bellow is my attempt, however order is kept unchanged. Added fields oi_number and vat_number are rendered at the end besides they are not at the end in self.fields SortedDict dictionary.
class ContactAdminForm(forms.ModelForm):
oi_number = fields_for_model(OrganizationExtra)['oi_number']
vat_number = fields_for_model(OrganizationExtra)['vat_number']
# fields = ('organization', 'oi_number', 'vat_number')
# ^^^ this won't affect fields order either
class Meta:
model = Organization
def __init__(self, *args, **kwargs):
super(ContactAdminForm, self).__init__(*args, **kwargs)
try:
org_ix = self.fields.keyOrder.index('organization')
self.fields.keyOrder.insert(org_ix+1, self.fields.keyOrder[-2])
self.fields.keyOrder.insert(org_ix+2, self.fields.keyOrder[-1])
del self.fields.keyOrder[-2:]
except ValueError:
pass
Does get the order of fields resolved before __init__ method is called ? How can I change their order ?
Update:
The above ModelForm is used as a form in admin model which defines its own fields, so if I put all fields definition in above form, I'll get FieldError exception about unknown field name:
class ContactAdminForm(forms.ModelForm):
...
class Meta:
model = Organization
fields = ('organization', 'oi_number', 'vat_number')
class ContactOptionsEx(ContactOptions):
form = ContactAdminForm
admin.site.register(Contact, ContactOptionsEx)
# at attempt to render the form:
# FieldError at /admin/contact/contact/3/
# Unknown field(s) (organization) specified for Organization
However the field named organization does exist and is available in ContactAdminForm.__init__ method.
The error
Unknown field(s) (organization) specified for Organization
does not refer to a field on your form, but to a field on the model (Organization).
I think the problem here is that you are trying to add fields from a different Model (OrganizationExtra) to the ModelForm for Organization. There is always a one-to-one relation between a ModelForm and a Model. If you want to edit a related instance in the admin, you can use inlines:
class OrganizationExtraInline(admin.StackedInline):
model = OrganizationExtra
class ContactOptionsEx(ContactOptions):
inlines = ContactOptions.inlines + [OrganizationExtraInline]
# ...
If you want to limit the inline to one instance, use a OneToOneField or max_num = 1
I have the following model and TabularInline subclasses:
class SomeModel(models.Model):
name = models.CharField(max_length=50)
class SomeModelInline(admin.TabularInline):
model = SomeModel
class SomeOtherModelAdmin(admin.ModelAdmin):
inlines = [SomeModelInline]
Without explicitly specifying the TabularInline's fields, Django's admin shows the fields "id" and "name". However, when I try and do:
class SomeModelInline(admin.TabularInline):
model = SomeModel
fields ['id','name']
Django throws the ImproperlyConfigured exception:
'SomeModelInline.fields' refers to field 'id' that is missing from the form.
What's going on here? Why can't I explicitly specify the id, even though Django's clearly capable of accessing it?
Ids are non-editable, by default inline shows the editable fields, but you can show the non-editable fields as well
From django docs
fields can contain values defined in ModelAdmin.readonly_fields to be
displayed as read-only.
So first add 'id' to readonly_fields, then add it to fields