self.cleaned_data.get not working with integer field - django

I have a pretty basic definition within my forms.py to pull data from my submitted form. Something I have done with other apps, but for some reason, it is not working at the moment. I feel like I'm overlooking something. Whenever I print(image_height) I get None.
models.py
height_field = models.IntegerField(default=1200)
forms.py
class PostForm(ModelForm):
class Meta:
model = Post
fields = ['user', 'title', 'content', 'post_image',
'height_field', 'width_field', 'draft', 'publish']
def clean_post_image(self):
post_image = self.cleaned_data["post_image"]
image_height = self.cleaned_data.get("height_field")
print(image_height)
return post_image

If you want to validate your form field with respect to other field, you should do that in your clean method.
From the docs,
The form subclass’s clean() method can perform validation that requires access to multiple form fields. This is where you might put in checks such as “if field A is supplied, field B must contain a valid email address”. This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.
You could do something like this,
def clean(self, cleaned_data):
post_image = cleaned_data.get("post_image")
height_image = cleaned_data.get("height_image")
#do_your_thing
return cleaned_data

Related

Basic tag system using ManyToManyField

I'm writing a simple Django powered notetaking app, and currently I'm implementing basic tagging system for my notes. The requirement is to have a Tag model with added_by field on it as a ForeignKey to User. First I have tried to modify django-taggit source, as well as extend its Tag model, however haven't had any success. So, I decided to implement my own tagging system from scratch. For this purpose I'm using a ManyToManyField:
class Note(models.Model):
slug = models.SlugField()
title = models.CharField(max_length=255)
[...]
tags = models.ManyToManyField(Tag)
This is my form:
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = [
'title',
'description',
'notebook',
'tags'
]
widgets = {
'tags': forms.TextInput()
}
Everything renders nicely, but when I try to submit the create form, I get an error:
So, inside that TextInput is a comma separated string. I assume that's not what Django expects, but I don't know how to convert that into something Django would accept.
I have tried to debug this, but it seems that my form_valid isn't getting executed at all. I have also tried to put some debug statements in clean_tags in my ModelForm, which is also not getting executed.
How can I overcome the issue?
My view (that currently doesn't handle any logic related to tags):
class NoteCreateView(LoginRequiredMixin, CreateView):
model = Note
form_class = NoteForm
def form_valid(self, form):
print(self.request.POST)
form.instance.added_by = self.request.user
return super().form_valid(form)
def get_initial(self):
return {'notebook': self.kwargs.get('pk')}
def get_success_url(self):
return reverse('notes:note-list', kwargs={'pk': self.kwargs['pk']})

django cbv dynamically exclude field from form based on is_staff / is_superuser

Been trying to determine the "most" elegant solution to dropping a field from a from if the user is not is_staff/is_superuser. Found one that works, with a minimal amount of code. Originally I though to add 'close' to the 'exclude' meta or use two different forms. But this seems to document what's going on. The logic is in the 'views.py' which is where I feel it blongs.
My question: Is this safe? I've not seen forms manipulated in this fashion, it works.
models.py
class Update(models.Model):
denial = models.ForeignKey(Denial)
user = models.ForeignKey(User)
action = models.CharField(max_length=1, choices=ACTION_CHOICES)
notes = models.TextField(blank=True, null=True)
timestamp = models.DateTimeField(default=datetime.datetime.utcnow().replace(tzinfo=utc))
close = models.BooleanField(default=False)
forms.py
class UpdateForm(ModelForm):
class Meta:
model = Update
exclude = ['user', 'timestamp', 'denial', ]
views.py
class UpdateView(CreateView):
model = Update
form_class = UpdateForm
success_url = '/denials/'
template_name = 'denials/update_detail.html'
def get_form(self, form_class):
form = super(UpdateView, self).get_form(form_class)
if not self.request.user.is_staff:
form.fields.pop('close') # ordinary users cannot close tickets.
return form
Yes, your approach is perfectly valid. The FormMixin was designed so you can override methods related to managing the form in the view and it is straightforward to test.
However, should yours or someone else's dynamic modifications of the resulting form object become too extensive, it would probably be best to define several form classes and use get_form_class() to pick the correct form class to instantiate the form object from.

Django - clean_field() is not being called for updates on admin inline form

I can not figure out why a clean_field() method is not being called for an inline form that is updated on an admin view. The code I have seems straight-forward (see synopsis below).
When I modify the Primary form through admin interface (http://admin/..../primary/1/), as expected, I see:
Admin.PrimaryAdminForm.clean_myfield() called
Admin.PrimaryAdminForm.clean() called
Model.Primary.clean() called
However, when I modify the Primary as seen as an inline on the Admin view of Membership (http://admin/..../membership/1/), I only see:
Model.Primary.clean() called
I have tried placing the "def clean_myfield(self):" method in the following locations but can not see it get executed from the Membership inlined Primary form:
Model.Primary.clean_myfield
Admin.PrimaryAdmin.clean_myfield
Admin.PrimaryAdminForm.clean_myfield
Admin.PrimaryAdminInline.clean_myfield
Is there somewhere else this clean_myfield code should be placed?
I have read (and reread) the Django docs on [forms and field validation][docs.djangoproject.com/en/dev/ref/forms/validation/#form-and-field-validation] which gives a great coverage, but there's nothing on inline validation. I've also read docs.djangoproject.com/en/dev/ref/contrib/admin/#adding-custom-validation-to-the-admin, but no help for inline specific validation. Is there other documentation on this?
---> Answered by Austin provided a doc reference to: "If not specified" (see his link) , which implies the answer. I added a request to improve the documents on this topic.
After further experimenting I found a workaround by putting code in the Model.Primary.clean() method:
def clean(self):
data = self.myfield
data += "_extra" # not actual cleaning code
self.myfield = data
So the question remains: Why is Model.clean() seem to be the only place to put admin inline form validation and not in a clean_myfield(self) method?
---> Answered by Austin. I needed add form = PrimaryAdminForm to PrimaryInline. With this addition, PrimaryAdminForm.clean_myfield(self) is called when PrimaryInline myfield is updated on Membership form. Code ordering was updated due to the added form reference.
Code synopsis:
No forms.py file -- all models are updated through admin interface
models.py:
class Membership(models.Model):
name = models.CharField( max_length=NAME_LENGTH,
null=True, blank=True, verbose_name="Membership Name Tag",
help_text="Name of membership" )
class Primary(models.Model):
user = models.OneToOneField(User, verbose_name="User Name")
membership = models.OneToOneField(Membership, verbose_name="Membership Name")
myfield = models.CharField("My Field", max_length=20, null=True, blank=True)
# clean method altered as in Update comment
# Why must this be here? Why not in clean_myfield(self)
def clean(self):
data = self.myfield
data += "_extra" # not actual cleaning code
self.myfield = data
admin.py:
class MembershipAdminForm(ModelForm):
class Meta:
model = Membership
class PrimaryAdminForm(ModelForm):
class Meta:
model = Primary
def clean_myfield(self):
data = self.cleaned_data['myfield']
data += "_extra" # not actual cleaning code
return unicode(data)
def clean(self):
cleaned_data = super(PrimaryAdminForm, self).clean()
# not actual cleaning code
return cleaned_data
# EDIT2: Moved PrimaryInline so it's defined after PrimaryAdminForm
class PrimaryInline(admin.StackedInline):
model = Primary
form = PrimaryAdminForm #EDIT2 as recommended by Austin
raw_id_fields = ['user']
verbose_name_plural = 'Primary Member'
fieldsets = ((None, {'classes': ('mbship', ),
'fields': ('user', 'myfield')}), )
class MembershipAdmin(admin.ModelAdmin):
form = MembershipAdminForm
# inlines
inlines = [PrimaryInline, ]
fieldsets = ((None, {'classes': ('mbship',),
'fields': ('name'), }), )
class PrimaryAdmin(admin.ModelAdmin):
form = PrimaryAdminForm
list_display = ('__unicode__', 'user', 'status', 'date_used' )
search_fields = ['user__first_name', 'user__last_name', 'user__email']
fieldsets = ((None, {'classes': ('mbship',),
'fields': ('user', 'membership', 'myfield'), }), )
def clean_myfield(self):
data = self.cleaned_data['myfield']
data += "_extra" # not actual cleaning code
return unicode(data)
Validation occurs on ModelForm objects, not ModelAdmin objects. If you want to override any clean methods then you have to create your own ModelForm descendent for each required model.
In your example, the PrimaryInline class does not specify a form. If not specified, the form used will be a ModelForm, which doesn't have any of your custom clean methods.
Try this:
class PrimaryInline(admin.StackedInline):
# ... existing code ...
form = PrimaryAdminForm
This will now use your custom PrimaryAdminForm with associated clean() methods.

Django admin BaseInlineFormSet validation after POST

Looking for info how django formsets validation works, though it is more complicated than it sounds. I have a formset with values, part of these values can be inserted there by javascript (it means they do not exist in database yet).
class RequireOneFormSet(BaseInlineFormSet):
def clean(self):
if any(self.errors):
return
form_count = len([f for f in self.forms if f.cleaned_data])
if form_count < 1:
raise ValidationError(_('At least one %(object)s is required.') %
{'object':
_(self.model._meta.object_name.lower())})
class VariantInline(admin.StackedInline):
model = Variant
extra = 1
formset = RequireOneFormSet
class ProductAdmin(admin.ModelAdmin):
class Meta:
model = Product
class Media:
js = (os.path.join(STATIC_URL, 'js', 'admin_utils.js'), )
exclude = ('slug',)
filter_horizontal = ('category',)
inlines = [ImageInline, DetailInline, VariantInline]
manufacturer = ModelChoiceField(Manufacturer.objects.all())
list_filter = ('name', 'manufacturer', 'category')
list_display = ('name', 'manufacturer')
search_fields = ('name',)
save_as = True
Next, basing on those entries I`d like to create objects during formset validation. Django complains that there is no such object in DB when 'Save' button is clicked.
I have tried to override clean method of model, clean of ModelAdmin, save_formset of formset but with no luck as these values created by javascript are filtered out earlier in process. I am looking for info which method takes care of that, and can it be overriden?
EDIT:
Added some code, used view is a generic one from Django.
I`ve managed to resolve it. Key was to create my own field and override clean() method there. As you can see in file django/forms/models.py in class ModelMultipleChoiceField clean() is responsible for checking send values.
class DetailsField(ModelMultipleChoiceField):
def clean(self, value):
(code here)
class VariantForm(ModelForm):
details = DetailsField(queryset=Detail.objects.all(),
widget=FilteredSelectMultiple('details', False))
class VariantInline(admin.StackedInline):
model = Variant
extra = 1
formset = RequireOneFormSet
form = VariantForm

field choices() as queryset?

I need to make a form, which have 1 select and 1 text input. Select must be taken from database.
model looks like this:
class Province(models.Model):
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=30)
def __unicode__(self):
return self.name
It's rows to this are added only by admin, but all users can see it in forms.
I want to make a ModelForm from that. I made something like this:
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=CHOICES),
}
but it doesn't work. The select tag is not displayed in html. What did I wrong?
UPDATE:
This solution works as I wanto it to work:
class ProvinceForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProvinceForm, self).__init__(*args, **kwargs)
user_provinces = UserProvince.objects.select_related().filter(user__exact=self.instance.id).values_list('province')
self.fields['name'].queryset = Province.objects.exclude(id__in=user_provinces).only('id', 'name')
name = forms.ModelChoiceField(queryset=None, empty_label=None)
class Meta:
model = Province
fields = ('name',)
Read Maersu's answer for the method that just "works".
If you want to customize, know that choices takes a list of tuples, ie (('val','display_val'), (...), ...)
Choices doc:
An iterable (e.g., a list or tuple) of
2-tuples to use as choices for this
field.
from django.forms.widgets import Select
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=( (x.id, x.name) for x in CHOICES )),
}
ModelForm covers all your needs (Also check the Conversion List)
Model:
class UserProvince(models.Model):
user = models.ForeignKey(User)
province = models.ForeignKey(Province)
Form:
class ProvinceForm(ModelForm):
class Meta:
model = UserProvince
fields = ('province',)
View:
if request.POST:
form = ProvinceForm(request.POST)
if form.is_valid():
obj = form.save(commit=True)
obj.user = request.user
obj.save()
else:
form = ProvinceForm()
If you need to use a query for your choices then you'll need to overwrite the __init__ method of your form.
Your first guess would probably be to save it as a variable before your list of fields but you shouldn't do that since you want your queries to be updated every time the form is accessed. You see, once you run the server the choices are generated and won't change until your next server restart. This means your query will be executed only once and forever hold your peace.
# Don't do this
class MyForm(forms.Form):
# Making the query
MYQUERY = User.objects.values_list('id', 'last_name')
myfield = forms.ChoiceField(choices=(*MYQUERY,))
class Meta:
fields = ('myfield',)
The solution here is to make use of the __init__ method which is called on every form load. This way the result of your query will always be updated.
# Do this instead
class MyForm(forms.Form):
class Meta:
fields = ('myfield',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the query here
MYQUERY = User.objects.values_list('id', 'last_name')
self.fields['myfield'] = forms.ChoiceField(choices=(*MYQUERY,))
Querying your database can be heavy if you have a lot of users so in the future I suggest some caching might be useful.
the two solutions given by maersu and Yuji 'Tomita' Tomita perfectly works, but there are cases when one cannot use ModelForm (django3 link), ie the form needs sources from several models / is a subclass of a ModelForm class and one want to add an extra field with choices from another model, etc.
ChoiceField is to my point of view a more generic way to answer the need.
The example below provides two choice fields from two models and a blank choice for each :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=([['','-'*10]]+[[x.id, x.__str__()] for x in Speakers.objects.all()]))
event = forms.ChoiceField(choices=( [['','-'*10]]+[[x.id, x.__str__()] for x in Events.objects.all()]))
If one does not need a blank field, or one does not need to use a function for the choice label but the model fields or a property it can be a bit more elegant, as eugene suggested :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=((x.id, x.__str__()) for x in Speakers.objects.all()))
event = forms.ChoiceField(choices=(Events.objects.values_list('id', 'name')))
using values_list() and a blank field :
event = forms.ChoiceField(choices=([['','-------------']] + list(Events.objects.values_list('id', 'name'))))
as a subclass of a ModelForm, using the one of the robos85 question :
class MixedForm(ProvinceForm):
speaker = ...