I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)
Related
I am working through a tutorial that includes the building of an articles app. I have an Article model that I am serializing and I am curious about why I need to explicitly set certain fields when using a ModelSerializer.
Here is my model:
from django.db import models
from core.models import TimestampedModel
class Article(TimestampedModel):
slug = models.SlugField(db_index=True, max_length=255, unique=True)
title = models.CharField(db_index=True, max_length=255)
description = models.TextField()
body = models.TextField()
author = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE, related_name='articles')
def __str__(self):
return self.title
Pretty standard stuff. Next step is to serialize the model data in my serializers.py file:
class ArticleSerializer(serializers.ModelSerializer):
author = ProfileSerializer(read_only=True) # Three fields from the Profile app
description = serializers.CharField(required=False)
slug = serializers.SlugField(required=False)
class Meta:
model = Article
fields = (
'author',
'body',
'createdAt',
'description',
'slug',
'title',
'updatedAt',
)
Specifically, why do I need to explicitly state the author, description, and slug fields if I am using serializers.ModelSerializer and pulling those fields in from my model in my class Meta: below?
Thanks!
In the Django-Rest-Framework documentation, drf-docs/model_serializer/specifying-which-fields-to-include it says:
If you only want a subset of the default fields to be used in a model serializer, you can do so using fields or exclude options, just as you would with a ModelForm. It is strongly recommended that you explicitly set all fields that should be serialized using the fields attribute. This will make it less likely to result in unintentionally exposing data when your models change.
Therefore by using fields = in the Serializer META, you can specify just the needed fields, and not returning vital fields like id, or exessive information like updated and created timestamps.
You can also instead of using fields, use exclude, which again takes in a tuple, but just excludes the fields you don't want.
These are especially useful when your database table contains a lot of information, returning all this information, especially if it is listed, can result in large return JSON's, where the frontend may only use a small percentage of the sent data.
DRF has designed their framework like this to specifically combat these problems.
In my opinion, we should define field in serializer for:
Your api use serializer don't need all data of your models. Then you can limit field can get by serializer. It faster if you have so much data.
You dont want public all field of your model. Example like id
Custom field in serializer like serializers.SerializerMethodField() must define in fields for work
Finally, iF you dont want, you can define serializer without define fields. Its will work normally
In order to be sure I force users to pick a valid value from a dropdown (rather than unknowingly leaving the first option in the list set without changing it to the correct value), I am inserting the blank choice field in many required form fields.
models.py
class MyModel(models.Model):
gender = models.CharField(max_length=1, null=True, blank=False, choices=GENDER_CHOICES, default='')
Original forms.py (Explicitly defining the form field. This works.)
from django.db.models.fields import BLANK_CHOICE_DASH
class MyForm(forms.ModelForm):
gender = forms.TypedChoiceField(required=True, choices=BLANK_CHOICE_DASH+MyModel._meta.get_field('gender').choices)
class Meta:
model = MyModel
fields = '__all__'
Now, because I am doing this for many fields, I tried to revise my earlier code in forms.py:
Revised forms.py (Setting the choices in __init__(). Does not work.)
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
for type in ('gender', ...):
self.fields[type].choices.insert(0, (u'',u'---------' ))
# Not needed anymore.. replaced by code in __init__()
# gender = forms.TypedChoiceField(required=True, choices=BLANK_CHOICE_DASH+MyModel._meta.get_field('gender').choices)
class Meta:
model = MyModel
fields = '__all__'
In this case, I do not get the dashes as the first choice in the dropdown.
I tried to look into the problem by using pdb in the template to inspect the form field right before it was outputted. The debugger shows element.field.choices to be (u'', u'---------'), ('f', 'Female'), ...] in both cases. Nevertheless, the code that outputs the form field inserts the dashes only in the original code, not in the revised code.
I tried stepping through the Django code that renders the form field to figure out if it was using some other field besides .choices that I'm not aware of, but I never stepped into the right part of the code.
Any ideas how to accomplish this? Although it works fine the first way, the revised code is much DRY-er... if only I could make it work! Thanks!
I'm struggling to figure out how best to approach this.
I have two models that need to be represented on one page within a form wizard:
class BookingItem(models.Model):
assignedChildren = models.ManyToManyField('PlatformUserChildren', related_name = "childIDs", null=True)
quantity = models.PositiveSmallIntegerField(max_length=2,blank=True, null=True)
class PlatformUserChildren(models.Model):
child_firstname = models.CharField('Childs first name', max_length=30,blank=True, null=True)
The relationship between the models when presenting them on the page is NOT one-to-one. It is governed by quantity attribute and therefore there may be more BookingItems than PlatformUserChildren objects presented on the page (e.g. 4 BookingItems and 2 PlatformUserChildren objects). I loop through each object multiple times based on quantity.
I also need to bind to a queryset of the PlatformChildUser model based on the current logged in user.
My question: how do I best present these two models on the first page of my form wizard?
I have looked at inline_formsets, but they rely on foreign key relationships only.
I have tried a modelformset_factory for one model, and an additional identifier for the other model with some backend reconciliation later, but I'm stuck on how to get a queryset based on user in there
i have attempted the get_form_instance method but I'm not 100% sure if it supports querysets
finally, I have attempted overloading init however most of the examples are not based on form wizard, and supply external arguments.
My current (rather vanilla) code is below:
forms.py
class checkout_PlatformUserChildren(forms.ModelForm):
#activity_id = forms.IntegerField()
class Meta:
model = PlatformUserChildren
fields = ('child_age','child_firstname')
class Meta:
model = PlatformUserChildren
fields = ('child_age','child_firstname')
widgets = {
'child_firstname': SelectMultiple(attrs={'class': 'form-control',}),
'child_age' : TextInput(attrs={'class': 'form-control',}),
}
checkout_PlatformUserChildrenFormSet = modelformset_factory(
PlatformUserChildren,
form = checkout_PlatformUserChildren,
fields=('child_firstname', 'child_age'),
extra=1, max_num=5, can_delete=True)
views.py (done method not shown)
note: getUser is an external function that is currently working
checkoutForms = [
("assign_child", checkout_PlatformUserChildrenFormSet),
("address_information", addressInfo),
]
checkoutTemplates = {
"assign_child": "checkout/assign_child.html",
"address_information": "checkout/address_information.html",
}
class checkout(SessionWizardView):
def get_form_instance(self, step):
currentUser = getUser(self.request.user.id)
if step == 'assign_child':
self.instance = currentUser
return self.instance
The following shows up instead of a field in my template.
<django.contrib.localflavor.us.forms.USStateSelect object at 0x92b136c>
my template has
{{ form.state }}
what could the issue be?
class RegistrationForm(forms.Form):
first_name = forms.CharField(max_length=20)
last_name = forms.CharField(max_length=20)
phone = USPhoneNumberField()
address1 = forms.CharField(max_length=45)
address2 = forms.CharField(max_length=45)
city = forms.CharField(max_length=50)
state = USStateSelect()
zip = USZipCodeField()
also is there anyway i can make the state and zip optional?
To limit the choices to a drop down list, use us.us_states.STATE_CHOICES in your model, and use us.forms.USStateField() instead of us.forms.USStateSelect() in your forms.
To make a field optional in a form, add blank = True to that field in the model.
from django.contrib.localflavor.us.us_states import STATE_CHOICES
from django.contrib.localflavor.us.models import USStateField
class ExampleLocation(models.Model):
address1 = models.CharField(max_length=45) #this is not optional in a form
address2 = models.CharField(max_length=45, blank = True) #this is made optional
state = USStateField(choices = STATE_CHOICES)
Instead of STATE_CHOICES, there are several options you can find in the localflavor documentation. STATE_CHOICES is the most inclusive, but that may not be what you desire. If you just want 50 states, plus DC, use US_STATES.
This answer assumes you're using ModelForms. If you aren't, you should be. Once you've made your model, you should follow DRY and create basic forms like so:
from django.forms import ModelForm
class ExampleForm(ModelForm):
class Meta:
model = ExampleLocation
And it inherits your fields from your model. You can customize what fields are available, if you don't want the whole model, with other class Meta options like fields or exclude. Model forms are just as customizable as any other form, they just start with the assumption of your model's fields.
I have models similar to the following:
class Band(models.Model):
name = models.CharField(unique=True)
class Event(models.Model):
name = models.CharField(max_length=50, unique=True)
bands = models.ManyToManyField(Band)
and essentially I want to use the validation capability offered by a ModelForm that already exists for Event, but I do not want to show the default Multi-Select list (for 'bands') on the page, because the potential length of the related models is extremely long.
I have the following form defined:
class AddEventForm(ModelForm):
class Meta:
model = Event
fields = ('name', )
Which does what is expected for the Model, but of course, validation could care less about the 'bands' field. I've got it working enough to add bands correctly, but there's no correct validation, and it will simply drop bad band IDs.
What should I do so that I can ensure that at least one (correct) band ID has been sent along with my form?
For how I'm sending the band-IDs with auto-complete, see this related question: Django ModelForm Validate custom Autocomplete for M2M, instead of ugly Multi-Select
You can override the default fields in a ModelForm.
class AddEventForm(forms.ModelForm):
band = forms.CharField(max_length=50)
def clean_band(self):
bands = Band.objects.filter(name=band,
self.data.get('band', ''))
if not bands:
raise forms.ValidationError('Please specify a valid band name')
self.cleaned_data['band_id'] = bands[0].id
Then you can use your autocomplete widget, or some other widget. You can also use a custom widget, just pass it into the band field definition: band = forms.CharField(widget=...)