Let me start out by saying this question is basic, and almost certainly has been answered elsewhere if I only could find exactly what I need.
I have users who will be entering dates in some screwy formats, which won't be recognized by the usual validation code for the DateField (for instance, 2014/4 which I will want to convert to 2014-4-01 internally). Do I need to mess with the code to clean, validate, or both for this, and if I redefine those functions do I need to explicitly call super?
My thought was that maybe I should give the model field and the form field different names, and somehow fill the model field with the form field data when I process the form... but the how of that is vague.
class Person(models.Model):
(some other fields)
date_of_arrival = models.DateField(blank=True, null=True)
class ClientForm(forms.ModelForm):
(some other fields)
date_of_arrival = forms.DateField(required=False, help_text="Date of arrival in town")
class Meta:
model = Person
fields = (..., date_of_arrival, ...)
$date = date('Y-m-d',$urvalue);
You need to implement a _clean() method in your form, where the name of the field is before the _, so in your example it would be date_of_arrival_clean().
In this method, do all your validations. I would recommend the dateutil package, which provides parse method that is designed to take a string that could possibly be a date, and convert it to a date. Using this method you will not have to write the mundane logic, instead your entire method would be:
def date_of_arrival_clean(self):
user_input = self.cleaned_data['date_of_arrival']
try:
d = parse(user_input)
except (ValueError, TypeError):
raise forms.ValidationError('{} is not a valid date'.format(user_input))
return user_input
My thought was that maybe I should give the model field and the form
field different names
You don't have to do this ... you can keep the same fields as in your model form and still implement the _clean() method as described above.
Keep in mind that if your form is a ModelForm, then django will also do database validation when try to validate the form.
Related
I try override clean method for model form with foreign key.
Model:
class Doc(Model):
name = CharField()
doc_type = ForeignKey(DictDocType)
Form:
class DocForm(ModelForm):
class Meta:
model = Doc
fields = '__all__'
def clean_doc_type(self)
doc_type_name = self.cleaned_data['doc_type']
try:
DictDocType.objects.get(name=doc_type_name)
except DictDocType.DoesNotExist:
msg = '{0} does not exist in dictdoc {1}.'.format(
doc_type_name, self.cleaned_data['name'])
raise ValidationError(msg)
return name
In the test I get an error:
KeyError: 'name'.
If I remove self.cleaned_data['name'] from msg - I do not get self.cleaned_data['doc_type'].
Where I'm wrong?
You can't cross reference other fields in clean_foo methods, because not all fields' clean_foo methods are called when you are in one of them. There might be some values of the form that are not populated yet, so clean_name() is not yet called when you call clean_doc_type(), thus you don't have self.cleaned_data['name'].
This should be done in clean method. Django doc very explicitly documented this:
By the time the form’s clean() method is called, all the individual
field clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far. So you also need to remember to allow for the fact that the
fields you are wanting to validate might not have survived the initial
individual field checks.
Also, your clean method doesn't make much sense and not necessary at all. You wouldn't able to choose a foreignkey that doesn't exist in ModelForm. Even if you force the front end to do so, the field would auto fail the validation and give error:
Select a valid choice. foo is not one of the available choices.
In Django Rest Framework 3, I want to return the unicode value of a pk relationship, the way you can using a serializer.StringRelatedField, but I need the value to be writable, too. StringRelatedField is read only.
I don't care if the API accepts the pk, or the string value, on the PUT (though accepting the string would be nifty, and would save me grabbing all the pks!). The API just needs to return the unicode string value on the GET.
I'm thinking PrimaryKeyRelatedField might be the way to go, but what does the query look like?
For instance, if the model I want is "Model", and I want Model.name to be serialized, what does this command look like:
name = serializers.PrimaryKeyRelatedField(queryset=Model.objects.get(pk=????))
I'm struggling because I don't know how to get the pk from the serializer object in order to query the related model ...
That's presuming PrimaryKeyRelatedField is what I need, of course. Which may be totally wrong.
Thanks
John
Here are example models as requested, slightly changed for clarity:
class CarModel(models.Model):
name = models.CharField(max_length=100,unique=True)
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=100)
make = models.ForeignKey(CarMake)
car_model = models.ForeignKey(CarModel)
class CarSerializer(serializers.ModelSerializer):
car_model = serializers.StringRelatedField() //like this, but read/write
class Meta:
model = Car
In this example I'm serializing Car, trying to return a string value of CarModel that can be updated with a select dropdown in a form.
If I use different serializers, one for POST that expects the PK and one for everything else that returns the string, the select directive in the form gets very messy.
So, ideally, I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK.
"I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK."
That would imply that 'name' should be unique. If it isn't unique then the lookup might return several instances. In the example you currently have 'name' isn't unique, but if it was then you could use...
car_model = serializers.SlugRelatedField(queryset=..., lookup_field='name')
I'm not convinced if that's actually what you want though. The best way to clarify these sorts of questions is typically to forget about the code for a moment, and just focus on a precise description of what you want the input and output representations to look like?...
In a Django app, I'm having a model Bet which contains a ManyToMany relation with the User model of Django:
class Bet(models.Model):
...
participants = models.ManyToManyField(User)
User should be able to start new bets using a form. Until now, bets have exactly two participants, one of which is the user who creates the bet himself. That means in the form for the new bet you have to chose exactly one participant. The bet creator is added as participant upon saving of the form data.
I'm using a ModelForm for my NewBetForm:
class NewBetForm(forms.ModelForm):
class Meta:
model = Bet
widgets = {
'participants': forms.Select()
}
def save(self, user):
... # save user as participant
Notice the redefined widget for the participants field which makes sure you can only choose one participant.
However, this gives me a validation error:
Enter a list of values.
I'm not really sure where this comes from. If I look at the POST data in the developer tools, it seems to be exactly the same as if I use the default widget and choose only one participant. However, it seems like the to_python() method of the ManyToManyField has its problems with this data. At least there is no User object created if I enable the Select widget.
I know I could work around this problem by excluding the participants field from the form and define it myself but it would be a lot nicer if the ModelForm's capacities could still be used (after all, it's only a widget change). Maybe I could manipulate the passed data in some way if I knew how.
Can anyone tell me what the problem is exactly and if there is a good way to solve it?
Thanks in advance!
Edit
As suggested in the comments: the (relevant) code of the view.
def new_bet(request):
if request.method == 'POST':
form = NewBetForm(request.POST)
if form.is_valid():
form.save(request.user)
... # success message and redirect
else:
form = NewBetForm()
return render(request, 'bets/new.html', {'form': form})
After digging in the Django code, I can answer my own question.
The problem is that Django's ModelForm maps ManyToManyFields in the model to ModelMultipleChoiceFields of the form. This kind of form field expects the widget object to return a sequence from its value_from_datadict() method. The default widget for ModelMultipleChoiceField (which is SelectMultiple) overrides value_from_datadict() to return a list from the user supplied data. But if I use the Select widget, the default value_from_datadict() method of the superclass is used, which simply returns a string. ModelMultipleChoiceField doesn't like that at all, hence the validation error.
To solutions I could think of:
Overriding the value_from_datadict() of Select either via inheritance or some class decorator.
Handling the m2m field manually by creating a new form field and adjusting the save() method of the ModelForm to save its data in the m2m relation.
The seconds solution seems to be less verbose, so that's what I will be going with.
I don't mean to revive a resolved question but I was working a solution like this and thought I would share my code to help others.
In j0ker's answer he lists two methods to get this to work. I used method 1. In which I borrowed the 'value_from_datadict' method from the SelectMultiple widget.
forms.py
from django.utils.datastructures import MultiValueDict, MergeDict
class M2MSelect(forms.Select):
def value_from_datadict(self, data, files, name):
if isinstance(data, (MultiValueDict, MergeDict)):
return data.getlist(name)
return data.get(name, None)
class WindowsSubnetForm(forms.ModelForm):
port_group = forms.ModelMultipleChoiceField(widget=M2MSelect, required=True, queryset=PortGroup.objects.all())
class Meta:
model = Subnet
The problem is that ManyToMany is the wrong data type for this relationship.
In a sense, the bet itself is the many-to-many relationship. It makes no sense to have the participants as a manytomanyfield. What you need is two ForeignKeys, both to User: one for the creator, one for the other user ('acceptor'?)
You can modify the submitted value before (during) validation in Form.clean_field_name. You could use this method to wrap the select's single value in a list.
class NewBetForm(forms.ModelForm):
class Meta:
model = Bet
widgets = {
'participants': forms.Select()
}
def save(self, user):
... # save user as participant
def clean_participants(self):
data = self.cleaned_data['participants']
return [data]
I'm actually just guessing what the value proivded by the select looks like, so this might need a bit of tweaking, but I think it will work.
Here are the docs.
Inspired by #Ryan Currah I found this to be working out of the box:
class M2MSelect(forms.SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
rendered = super(M2MSelect, self).render(name, value=value, attrs=attrs, choices=choices)
return rendered.replace(u'multiple="multiple"', u'')
The first one of the many to many is displayed and when saved only the selected value is left.
I found an easyer way to do this inspired by #Ryan Currah:
You just have to override "allow_multiple_selected" attribut from SelectMultiple class
class M2MSelect(forms.SelectMultiple):
allow_multiple_selected = False
class NewBetForm(forms.ModelForm):
class Meta:
model = Bet
participants = forms.ModelMultipleChoiceField(widget=M2MSelect, required=True, queryset=User.objects.all())
I posted this question on the django-users list, but haven't had a reply there yet.
I have models that look something like this:
class ProductGroup(models.Model):
name = models.CharField(max_length=10, primary_key=True)
def __unicode__(self): return self.name
class ProductRun(models.Model):
date = models.DateField(primary_key=True)
def __unicode__(self): return self.date.isoformat()
class CatalogItem(models.Model):
cid = models.CharField(max_length=25, primary_key=True)
group = models.ForeignKey(ProductGroup)
run = models.ForeignKey(ProductRun)
pnumber = models.IntegerField()
def __unicode__(self): return self.cid
class Meta:
unique_together = ('group', 'run', 'pnumber')
class Transaction(models.Model):
timestamp = models.DateTimeField()
user = models.ForeignKey(User)
item = models.ForeignKey(CatalogItem)
quantity = models.IntegerField()
price = models.FloatField()
Let's say there are about 10 ProductGroups and 10-20 relevant
ProductRuns at any given time. Each group has 20-200 distinct
product numbers (pnumber), so there are at least a few thousand
CatalogItems.
I am working on formsets for the Transaction model. Instead of a
single select menu with the several thousand CatalogItems for the
ForeignKey field, I want to substitute three drop-down menus, for
group, run, and pnumber, which uniquely identify the CatalogItem.
I'd also like to limit the choices in the second two drop-downs to
those runs and pnumbers which are available for the currently
selected product group (I can update them via AJAX if the user
changes the product group, but it's important that the initial page
load as described without relying on AJAX).
What's the best way to do this?
As a point of departure, here's what I've tried/considered so far:
My first approach was to exclude the item foreign key field from the
form, add the substitute dropdowns by overriding the add_fields
method of the formset, and then extract the data and populate the
fields manually on the model instances before saving them. It's
straightforward and pretty simple, but it's not very reusable and I
don't think it is the right way to do this.
My second approach was to create a new field which inherits both
MultiValueField and ModelChoiceField, and a corresponding
MultiWidget subclass. This seems like the right approach. As
Malcolm Tredinnick put it in
a django-users discussion,
"the 'smarts' of a field lie in the Field class."
The problem I'm having is when/where to fetch the lists of choices
from the db. The code I have now does it in the Field's __init__,
but that means I have to know which ProductGroup I'm dealing with
before I can even define the Form class, since I have to instantiate the
Field when I define the form. So I have a factory
function which I call at the last minute from my view--after I know
what CatalogItems I have and which product group they're in--to
create form/formset classes and instantiate them. It works, but I
wonder if there's a better way. After all, the field should be
able to determine the correct choices much later on, once it knows
its current value.
Another problem is that my implementation limits the entire formset
to transactions relating to (CatalogItems from) a single
ProductGroup.
A third possibility I'm entertaining is to put it all in the Widget
class. Once I have the related model instance, or the cid, or
whatever the widget is given, I can get the ProductGroup and
construct the drop-downs. This would solve the issues with my
second approach, but doesn't seem like the right approach.
One way of setting field choices of a form in a formset is in the form's __init__ method by overwriting the self.fields['field_name'].choices, but since a more dynamic approach is desired, here is what works in a view:
from django.forms.models import modelformset_factory
user_choices = [(1, 'something'), (2, 'something_else')] # some basic choices
PurchaserChoiceFormSet = modelformset_factory(PurchaserChoice, form=PurchaserChoiceForm, extra=5, max_num=5)
my_formset = PurchaserChoiceFormSet(self.request.POST or None, queryset=worksheet_choices)
# and now for the magical for loop
for choice_form in my_formset:
choice_form.fields['model'].choices = user_choices
I wasn't able to find the answer for this but tried it out and it works in Django 1.6.5. I figured it out since formsets and for loops seem to go so well together :)
I ended up sticking with the second approach, but I'm convinced now that it was the Short Way That Was Very Long. I had to dig around a bit in the ModelForm and FormField innards, and IMO the complexity outweighs the minimal benefits.
What I wrote in the question about the first approach, "It's straightforward and pretty simple," should have been the tip-off.
I would like to provide context help for the input fields in my forms ("First name": "Your first name. Please enter all of them if you have several."). Instead of hard-coding them in source code, I would like to make those help texts editable through the admin interface. My idea is to somehow extend the field class (include a new attribute similar to verbose_name) and store that in the database (probably a three-column table 'Model, Field, Help' would be sufficient).
However, I don't know whether this is feasible or has been done before. Do you? Could you give me some to where to start if it has been not?
Every field in a form already contains help_text, though it should be declared as a parameter in the field, in the Form class.
E.g.,
class SomeForm(forms.Form):
some_field1 = forms.CharField(verbose_name="Some Field 1", max_length=100, help_text="Please the first field.")
some_field2 = forms.CharField(verbose_name="Some Field 2", max_length=100, help_text="Please the second field.")
Personally, I don't see the benefit of having it in the database rather than in the form tied to the field.
EDIT:
So you can override the help text. Let's say first imagine you had a dictionary for each form you want to override help_text in a form. Before rendering the Context, you could reprocess the form with the dictionary as such:
my_form = SomeForm()
for field_name, new_help_text in my_form_override_help_text_dict.items():
my_form.fields[field_name].help_text = new_help_text
and then add my_form to the context before rendering it.
Now where and how you want to store the help text is your choice; e.g., your solution of creating a ModelFieldHelp with three char fields (Model Name, Field Name, Help Text) would work, then you need something like
class ModelHelpField(models.Model):
model_name = CharField(max_length=50)
field_name = CharField(max_length=50)
new_help_text = CharField(max_length=50)
field_help_qs= ModelHelpField.objects.filter(model_name='SomeModel')
my_form_override_help_text_dict = dict([(mfh.field_name, mfh.new_help_text) for mfh in field_help_qs])
Now it may make sense to automate this process for all your models that you create forms for, by defining a function in the form or model that automatically creates these ModelHelpFields (if not defined) and updates itself with the current help text after being initialized ...